import express from "express"; import fs from "fs"; import bodyParser from "body-parser"; import cors from "cors"; import axios from "axios"; import path from "path"; import _ from "lodash"; import dotenv from "dotenv"; dotenv.config(); const metaPath = path.join(process.cwd(), "meta.json"); const dataFile = "data.json"; const cachePrefix = "product-cache-"; const app = express(); // Cho phép tất cả origin gọi API app.use(cors()); // parse JSON body app.use(bodyParser.json()); // API lưu dữ liệu app.post("/sync", (req, res) => { const data = req.body; if (!Array.isArray(data)) { return res.status(400).json({ error: "Dữ liệu phải là array" }); } // Chuẩn hoá dữ liệu (có thêm date) const mapped = data.map((item) => ({ ...item, date: new Date().toISOString(), })); // Đọc dữ liệu cũ let oldData = []; try { oldData = JSON.parse(fs.readFileSync(dataFile, "utf-8")); } catch (e) { oldData = []; } // So sánh (bỏ qua field date vì nó luôn khác) const stripDate = (arr) => arr.map(({ date, ...rest }) => rest); const oldStripped = stripDate(oldData); const newStripped = stripDate(mapped); if (!_.isEqual(oldStripped, newStripped)) { // Nếu khác → xoá cache const files = fs.readdirSync("."); files.forEach((file) => { if (file.startsWith(cachePrefix)) { fs.unlinkSync(path.join(".", file)); } }); console.log("Dữ liệu thay đổi → xoá cache"); } else { console.log("Dữ liệu không đổi → giữ cache"); } // Ghi dữ liệu mới fs.writeFileSync(dataFile, JSON.stringify(mapped, null, 2)); res.json({ message: "Đã lưu dữ liệu thành công", saved: mapped.length }); }); // API đọc lại dữ liệu app.get("/data", (req, res) => { if (!fs.existsSync("data.json")) { return res.json([]); } const content = fs.readFileSync("data.json", "utf-8"); res.json(JSON.parse(content)); }); app.post("/", async (req, res) => { try { // ===== Helpers ===== const readJSON = (path, fallback = null) => { try { return JSON.parse(fs.readFileSync(path, "utf-8")); } catch { return fallback; } }; const writeJSON = (path, data) => { try { fs.writeFileSync(path, JSON.stringify(data, null, 2)); } catch (e) { console.warn("Không ghi được file:", e.message); } }; // ===== Meta ===== let meta = readJSON(metaPath, { total: 4000 }); const filter = req.body?.filter || {}; const originalFilter = { ...filter }; const status = filter?.where?.status_listing; if (["listed", "unlisted"].includes(status)) { filter.skip = 0; } // ===== Cache check ===== const cacheFile = `${cachePrefix}${JSON.stringify(originalFilter)}.json`; if (fs.existsSync(cacheFile)) { return res.json(readJSON(cacheFile, {})); } // ===== Load publisted data nếu cần ===== let publistedData = []; if (status && meta.total) { filter.limit = meta.total; publistedData = readJSON("data.json", []); } // ===== Call API gốc ===== const { data } = await axios({ headers: { Authorization: req.headers.authorization }, url: "transferGetData", baseURL: process.env.BASE_URL, method: "POST", data: { ...req.body, filter }, }); // Update meta if (typeof data.total === "number") { writeJSON(metaPath, { total: data.total }); } // ===== Xử lý listed/unlisted ===== if (status === "listed" || status === "unlisted") { const skip = originalFilter.skip || 0; const limit = originalFilter.limit || data.data.length; const listedCodes = new Set( publistedData.map((i) => (i.title + i.price || "").trim().toLowerCase()) ); let filteredData = data.data.filter((item) => { const key = `${item.name} - ${item.code}${item.price}`.toLowerCase(); return status === "listed" ? listedCodes.has(key) : !listedCodes.has(key); }); data.total = filteredData.length; data.data = filteredData.slice(skip, skip + limit); data.filter = { ...originalFilter, skip, limit }; writeJSON(cacheFile, data); } res.json(data); } catch (err) { console.error("API error:", err.message); res.status(500).json({ error: err.message }); } }); app.listen(3000, () => { console.log("Server chạy tại http://localhost:3000"); });