auto-post-marketplace-facebook/server/server.js

212 lines
5.7 KiB
JavaScript

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 productsFile = path.join(process.cwd(), "products.json");
const app = express();
// Cho phép tất cả origin gọi API
app.use(cors());
app.use(express.json({ limit: "10mb" }));
// parse JSON body
app.use(bodyParser.json());
// POST thêm product (tự động update nếu id đã tồn tại)
app.post("/products", (req, res) => {
const { id, ...rest } = req.body;
if (!id) return res.status(400).json({ error: "Product must have an id" });
// Đọc dữ liệu cũ (nếu có)
let data = {};
if (fs.existsSync(productsFile)) {
data = JSON.parse(fs.readFileSync(productsFile, "utf8") || "{}");
}
// Thêm / ghi đè theo id
data[id] = { id, ...rest };
// Ghi lại file
fs.writeFileSync(productsFile, JSON.stringify(data, null, 2));
res.json({ success: true, product: data[id] });
});
// GET product theo id
app.get("/products/:id", (req, res) => {
if (!fs.existsSync(productsFile)) return res.json({});
const data = JSON.parse(fs.readFileSync(productsFile, "utf8") || "{}");
res.json(data[req.params.id] || null);
});
// 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 { info, ...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;
let filteredData = data.data.filter((item) => {
const key = item?.title.includes(item.code)
? `${item.title}`.toLowerCase()
: `${item.title} - ${item.code}`.toLowerCase();
if (status === "listed") {
return publistedData.some(
(i) =>
`${i.title}`.toLowerCase().includes(key) &&
i?.url_info == info?.url
);
} else {
return !publistedData.some(
(i) =>
`${i.title}`.toLowerCase().includes(key) &&
i?.url_info == info?.url
);
}
});
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 });
}
});
// Route trả về privacy policy
app.get("/policy", (req, res) => {
res.sendFile(path.join(process.cwd(), "views", "policy.html"));
});
app.listen(3000, () => {
console.log("Server chạy tại http://localhost:3000");
});