966a80c690
Guard the finally block's re-await of browserPromise so a rejected launch (e.g. Playwright can't start Chromium) doesn't rethrow out of finally and override the try block's already-successful return value. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013gwc3aCiyQxmCWQke8RvEz
94 lines
3.2 KiB
JavaScript
94 lines
3.2 KiB
JavaScript
// Leest een .env-bestand in voor lokale ontwikkeling; in productie komen de
|
|
// env vars van Coolify. quiet houdt de opstartlogging schoon.
|
|
require('dotenv').config({ quiet: true });
|
|
|
|
const path = require('node:path');
|
|
const {
|
|
openDb, listProducts, addProduct, deleteProduct,
|
|
recordCheckSuccess, recordCheckFailure,
|
|
} = require('./src/db');
|
|
const { fetchAndExtractPrice } = require('./src/scraper');
|
|
const { launchBrowser, fetchAndExtractPriceViaBrowser } = require('./src/browserScraper');
|
|
const { createTransport, sendPriceChangeEmail } = require('./src/mailer');
|
|
const { checkAllProducts } = require('./src/checkProducts');
|
|
const { start: startScheduler } = require('./src/scheduler');
|
|
const { createApp } = require('./src/app');
|
|
|
|
const REQUIRED_ENV = ['BASIC_AUTH_USER', 'BASIC_AUTH_PASS', 'SMTP_FROM', 'NOTIFY_EMAIL'];
|
|
const missing = REQUIRED_ENV.filter((name) => !process.env[name]);
|
|
if (missing.length > 0) {
|
|
console.error(`Ontbrekende verplichte environment variables: ${missing.join(', ')}`);
|
|
console.error('Zet ze (bijvoorbeeld in .env, zie .env.example) en start opnieuw.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
const DB_PATH = process.env.DB_PATH || path.join(__dirname, 'data', 'korting.db');
|
|
|
|
const db = openDb(DB_PATH);
|
|
const transport = createTransport(process.env);
|
|
|
|
function summarize(results) {
|
|
const counts = {};
|
|
for (const result of results) {
|
|
counts[result.status] = (counts[result.status] || 0) + 1;
|
|
}
|
|
const parts = Object.entries(counts).map(([status, count]) => `${status}=${count}`);
|
|
return parts.length > 0 ? parts.join(' ') : 'geen producten';
|
|
}
|
|
|
|
async function runCheck() {
|
|
const products = listProducts(db);
|
|
let browserPromise = null;
|
|
function getBrowser() {
|
|
if (!browserPromise) {
|
|
browserPromise = launchBrowser();
|
|
}
|
|
return browserPromise;
|
|
}
|
|
try {
|
|
const deps = {
|
|
fetchAndExtractPrice,
|
|
fetchAndExtractPriceViaBrowser: async (url) => {
|
|
const browser = await getBrowser();
|
|
return fetchAndExtractPriceViaBrowser(browser, url);
|
|
},
|
|
sendMail: (payload) => sendPriceChangeEmail(transport, {
|
|
from: process.env.SMTP_FROM,
|
|
to: process.env.NOTIFY_EMAIL,
|
|
...payload,
|
|
}),
|
|
recordCheckFailure: (id, status) => recordCheckFailure(db, id, status),
|
|
recordCheckSuccess: (id, data) => recordCheckSuccess(db, id, data),
|
|
now: () => new Date().toISOString(),
|
|
};
|
|
const results = await checkAllProducts(products, deps);
|
|
console.log(`[check] ${products.length} product(en) gecontroleerd: ${summarize(results)}`);
|
|
return results;
|
|
} finally {
|
|
if (browserPromise) {
|
|
const browser = await browserPromise.catch(() => null);
|
|
if (browser) {
|
|
await browser.close().catch((err) => {
|
|
console.error(`[check] kon de browser niet netjes sluiten: ${err && err.message}`);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
startScheduler(runCheck);
|
|
|
|
const app = createApp({
|
|
listProducts: () => listProducts(db),
|
|
addProduct: (data) => addProduct(db, data),
|
|
deleteProduct: (id) => deleteProduct(db, id),
|
|
checkAllProducts: runCheck,
|
|
authUser: process.env.BASIC_AUTH_USER,
|
|
authPass: process.env.BASIC_AUTH_PASS,
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Korting draait op poort ${PORT}`);
|
|
});
|