feat: complete node task 2a
This commit is contained in:
+323
-1
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user