feat: complete node task 2a

This commit is contained in:
Ayobami
2025-07-28 06:42:00 +01:00
parent fe95626d9f
commit bd70df60b9
17 changed files with 1968 additions and 170 deletions
+323 -1
View File
@@ -8,6 +8,25 @@ const airportData = JSON.parse(
);
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) {
@@ -60,9 +79,20 @@ router.get("/airports", function (req, res) {
res.json(matches);
});
// Log analytic event
// 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
@@ -162,4 +192,296 @@ router.post("/coin-calc", function (req, res) {
}
});
// 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(`
<html>
<head><title>Pay for Analytics</title></head>
<body style="display:flex;align-items:center;justify-content:center;height:100vh;flex-direction:column;">
<h2>Rate limit exceeded</h2>
<p>You need to pay $5 to continue using analytics.</p>
<button id="checkout">Pay with Stripe</button>
<script src="https://js.stripe.com/v3/"></script>
<script>
document.getElementById('checkout').onclick = function() {
var stripe = Stripe('${STRIPE_PUBLIC_KEY}');
stripe.redirectToCheckout({ sessionId: '${session.id}' });
}
</script>
</body>
</html>
`);
});
// 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;