var express = require("express"); var router = express.Router(); var path = require("path"); const fetch = require("node-fetch"); const fs = require("fs"); const airportData = JSON.parse( fs.readFileSync(path.join(__dirname, "../airportdata.json"), "utf8") ); const db = require("../models"); const { create } = require("xmlbuilder2"); const rateLimitMap = new Map(); const multer = require("multer"); const uploadDir = path.join(__dirname, "../public/uploads"); if (!fs.existsSync(uploadDir)) fs.mkdirSync(uploadDir, { recursive: true }); const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, uploadDir); }, filename: function (req, file, cb) { const unique = Date.now() + "-" + Math.round(Math.random() * 1e9); cb(null, unique + "-" + file.originalname); }, }); const uploadMulter = multer({ storage }); const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); const redis = require("redis"); const client = redis.createClient(); client.connect().catch(console.error); /* GET home page. */ router.get("/", function (req, res, next) { res.sendFile(path.join(__dirname, "../views/index.html")); }); // Weather API route router.get("/api/weather", async function (req, res) { try { // Replace with your actual API key and city const apiKey = process.env.WEATHER_API_KEY; const city = "Toronto"; const url = `http://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}&aqi=no`; const response = await fetch(url); if (!response.ok) throw new Error("Weather API error"); const data = await response.json(); const temp_c = data.current.temp_c; const text = data.current.condition.text.toLowerCase(); let condition = "sunny"; if (text.includes("rain")) condition = "rain"; else if (text.includes("snow")) condition = "snow"; res.json({ temp_c, condition }); } catch (err) { res.status(500).json({ error: "Failed to fetch weather" }); console.error(err); } }); // UTC time API route router.get("/api/utc", function (req, res) { res.json({ utc: new Date().toISOString() }); }); // Autocomplete airports router.get("/airports", function (req, res) { const search = (req.query.search || "").trim(); if (search.length < 3) { return res .status(400) .json({ error: "Search term must be at least 3 characters" }); } const term = search.toLowerCase(); const matches = airportData .filter( (a) => (a.name && a.name.toLowerCase().includes(term)) || (a.code && a.code.toLowerCase().includes(term)) ) .slice(0, 10); res.json(matches); }); // Log analytic event with rate limiting router.post("/analytic", async function (req, res) { try { const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress; const now = Date.now(); const windowMs = 60 * 1000; const maxReq = 10; if (!rateLimitMap.has(ip)) rateLimitMap.set(ip, []); let timestamps = rateLimitMap.get(ip).filter((ts) => now - ts < windowMs); if (timestamps.length >= maxReq) { return res.status(429).json({ redirect: "/pay" }); } timestamps.push(now); rateLimitMap.set(ip, timestamps); const { widget_name, browser_type } = req.body; if (!widget_name || !browser_type) { return res .status(400) .json({ error: "widget_name and browser_type required" }); } await db.analytic.create({ widget_name, browser_type }); res.json({ success: true }); } catch (err) { res.status(500).json({ error: "Failed to log analytic" }); } }); // Count analytic rows router.get("/analytic/count", async function (req, res) { try { const count = await db.analytic.count(); res.json({ count }); } catch (err) { res.status(500).json({ error: "Failed to count analytics" }); } }); // Export analytic as XML router.get("/analytic/export", async function (req, res) { try { const analytics = await db.analytic.findAll({ raw: true }); const root = create({ version: "1.0" }).ele("analytics"); analytics.forEach((a) => { root .ele("analytic") .ele("id") .txt(a.id) .up() .ele("create_at") .txt(a.create_at) .up() .ele("widget_name") .txt(a.widget_name) .up() .ele("browser_type") .txt(a.browser_type) .up() .up(); }); const xml = root.end({ prettyPrint: true }); res.set("Content-Type", "application/xml"); res.set("Content-Disposition", 'attachment; filename="analytics.xml"'); res.send(xml); } catch (err) { res.status(500).json({ error: "Failed to export analytics" }); } }); // Reddit widget route router.get("/reddit", async function (req, res) { try { const response = await fetch("https://www.reddit.com/r/programming.json"); if (!response.ok) throw new Error("Reddit API error"); const data = await response.json(); const posts = (data.data.children || []) .map((c) => c.data) .filter((_, i) => i % 2 === 0) .slice(0, 4) .map((post) => ({ title: post.title, url: "https://reddit.com" + post.permalink, author: post.author, })); res.json(posts); } catch (err) { res.status(500).json({ error: "Failed to fetch reddit posts" }); } }); // Coin calculator route router.post("/coin-calc", function (req, res) { try { let { amount } = req.body; amount = parseFloat(amount); if (isNaN(amount) || amount < 0) return res.status(400).json({ error: "Invalid amount" }); const denoms = [20, 10, 5, 1, 0.25, 0.1, 0.05, 0.01]; const result = []; let remaining = Math.round(amount * 100); // work in cents for (let d of denoms) { let denomCents = Math.round(d * 100); let count = Math.floor(remaining / denomCents); if (count > 0) { result.push({ denomination: d, count }); remaining -= count * denomCents; } } res.json(result); } catch (err) { res.status(500).json({ error: "Failed to calculate coins" }); } }); // Stripe payment page router.get("/pay", async function (req, res) { const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: [ { price_data: { currency: "usd", product_data: { name: "Widget Analytics Access" }, unit_amount: 500, }, quantity: 1, }, ], mode: "payment", success_url: req.protocol + "://" + req.get("host") + "/?paid=1", cancel_url: req.protocol + "://" + req.get("host") + "/pay?cancel=1", }); res.send(`
You need to pay $5 to continue using analytics.
`); }); // Upload image router.post("/upload", uploadMulter.single("image"), async function (req, res) { try { if (!req.file) return res.status(400).json({ error: "No file uploaded" }); const file = req.file; const dbUpload = await db.upload.create({ filename: file.filename, mimetype: file.mimetype, path: "/uploads/" + file.filename, }); res.json({ url: "/uploads/" + file.filename }); } catch (err) { res.status(500).json({ error: "Failed to upload image" }); } }); // Get latest uploaded image router.get("/upload/latest", async function (req, res) { try { const latest = await db.upload.findOne({ order: [["created_at", "DESC"]] }); if (!latest) return res.json({ url: null }); res.json({ url: latest.path }); } catch (err) { res.status(500).json({ error: "Failed to fetch latest upload" }); } }); // Chat routes router.post("/send", async function (req, res) { try { const { message } = req.body; if (!message) return res.status(400).json({ error: "Message required" }); const chatMessage = { message, timestamp: new Date().toISOString(), id: Date.now(), }; await client.lPush("chatroom", JSON.stringify(chatMessage)); res.json({ success: true }); } catch (err) { res.status(500).json({ error: "Failed to send message" }); } }); router.get("/chat/all", async function (req, res) { try { const messages = await client.lRange("chatroom", 0, -1); const parsedMessages = messages.map((msg) => JSON.parse(msg)); res.json(parsedMessages); } catch (err) { res.status(500).json({ error: "Failed to fetch messages" }); } }); router.get("/poll", async function (req, res) { try { const messageCount = await client.lLen("chatroom"); const lastCheck = req.query.lastCheck || 0; if (messageCount > lastCheck) { res.json({ updated: true, count: messageCount }); } else { res.json({ updated: false, count: messageCount }); } } catch (err) { res.status(500).json({ error: "Poll failed" }); } }); router.post("/chat/save", async function (req, res) { try { const messages = await client.lRange("chatroom", 0, -1); const chatMessages = JSON.stringify(messages.map((msg) => JSON.parse(msg))); await db.chat.create({ chat_messages: chatMessages, }); res.json({ success: true }); } catch (err) { res.status(500).json({ error: "Failed to save chat" }); } }); // Chat page router.get("/chat", function (req, res) { res.sendFile(path.join(__dirname, "../views/chat.html")); }); // Flow Builder routes router.get("/flow", function (req, res) { res.sendFile(path.join(__dirname, "../views/flow.html")); }); // Get all flows router.get("/flows", async function (req, res) { try { const flows = await db.flow.findAll({ order: [["created_at", "DESC"]], }); res.json(flows); } catch (err) { res.status(500).json({ error: "Failed to fetch flows" }); } }); // Create new flow router.post("/flow", async function (req, res) { try { const { name, description } = req.body; if (!name) return res.status(400).json({ error: "Flow name required" }); const flow = await db.flow.create({ name, description }); res.json({ id: flow.id, name: flow.name }); } catch (err) { res.status(500).json({ error: "Failed to create flow" }); } }); // Add task to flow router.post("/flow/:id/task", async function (req, res) { try { const flowId = req.params.id; const { action_type, input_data, order_index } = req.body; if (!action_type || !input_data || order_index === undefined) { return res .status(400) .json({ error: "action_type, input_data, and order_index required" }); } const task = await db.task.create({ flow_id: flowId, action_type, input_data, order_index, }); res.json({ id: task.id, action_type: task.action_type }); } catch (err) { res.status(500).json({ error: "Failed to add task" }); } }); // Get flow details router.get("/flow/:id", async function (req, res) { try { const flowId = req.params.id; const flow = await db.flow.findByPk(flowId); const tasks = await db.task.findAll({ where: { flow_id: flowId }, order: [["order_index", "ASC"]], }); res.json({ flow, tasks }); } catch (err) { res.status(500).json({ error: "Failed to fetch flow" }); } }); // Execute flow router.post("/flow/:id/execute", async function (req, res) { try { const flowId = req.params.id; const { payload } = req.body; const flow = await db.flow.findByPk(flowId); const tasks = await db.task.findAll({ where: { flow_id: flowId }, order: [["order_index", "ASC"]], }); const results = []; for (const task of tasks) { let result = ""; let status = "success"; try { switch (task.action_type) { case "send_test_mail": // Simulate sending email result = `Email sent to: ${task.input_data}`; break; case "http_get_request": const response = await fetch(task.input_data); result = `HTTP GET ${task.input_data}: ${response.status}`; break; case "mysql_select": const [table, id] = task.input_data.split("|"); const query = `SELECT * FROM ${table} WHERE id = ${id}`; result = `Query executed: ${query}`; break; case "drive_upload": result = `File uploaded to Google Drive with content: ${task.input_data}`; break; default: result = `Unknown action type: ${task.action_type}`; status = "error"; } } catch (err) { result = `Error: ${err.message}`; status = "error"; } // Log the execution await db.flow_log.create({ flow_id: flowId, task_id: task.id, result, status, }); results.push({ task_id: task.id, result, status }); } res.json({ flow_id: flowId, results }); } catch (err) { res.status(500).json({ error: "Failed to execute flow" }); } }); // Webhook trigger router.get("/flow/:id/trigger", async function (req, res) { try { const flowId = req.params.id; const payload = req.query.payload; if (!payload) { return res.status(400).json({ error: "Payload required" }); } // Execute the flow with the payload const response = await fetch( `${req.protocol}://${req.get("host")}/flow/${flowId}/execute`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ payload }), } ); const result = await response.json(); res.json(result); } catch (err) { res.status(500).json({ error: "Failed to trigger flow" }); } }); module.exports = router;