xiaohongshufabu/server/routes/products.js
2026-07-21 21:17:20 +08:00

207 lines
8.1 KiB
JavaScript

const express = require('express');
const { v4: uuidv4 } = require('uuid');
const fs = require('fs');
const path = require('path');
function createProductRoutes(db) {
const router = express.Router();
// List products (optional filter by shop_id)
router.get('/', (req, res) => {
const { shop_id } = req.query;
let products;
if (shop_id) {
products = db.prepare('SELECT * FROM products WHERE shop_id = ? ORDER BY created_at DESC').all(shop_id);
} else {
products = db.prepare('SELECT * FROM products ORDER BY created_at DESC').all();
}
// Parse shop info
const shopStmt = db.prepare('SELECT shop_name FROM shops WHERE id = ?');
products = products.map(p => ({
...p,
shop_name: shopStmt.get(p.shop_id)?.shop_name || 'Unknown'
}));
res.json(products);
});
// Export products (optional filter by shop_id)
router.get('/export', (req, res) => {
const { shop_id } = req.query;
let products;
if (shop_id) {
products = db.prepare('SELECT p.*, s.shop_name FROM products p LEFT JOIN shops s ON p.shop_id = s.id WHERE p.shop_id = ? ORDER BY p.created_at DESC').all(shop_id);
} else {
products = db.prepare('SELECT p.*, s.shop_name FROM products p LEFT JOIN shops s ON p.shop_id = s.id ORDER BY p.created_at DESC').all();
}
res.setHeader('Content-Disposition', 'attachment; filename=products.json');
res.json(products);
});
// Upload reference image for a product
router.post('/:id/image', (req, res) => {
const product = db.prepare('SELECT * FROM products WHERE id = ?').get(req.params.id);
if (!product) return res.status(404).json({ error: 'Product not found' });
const { image } = req.body;
if (!image) return res.status(400).json({ error: 'image (base64) required' });
let imagesRoot = path.join(__dirname, '..', 'images');
try {
const savePathRow = db.prepare("SELECT value FROM configs WHERE key = 'image_save_path'").get();
if (savePathRow?.value?.trim()) imagesRoot = savePathRow.value.trim();
} catch (_) {}
const imagesDir = path.join(imagesRoot, product.id);
if (!fs.existsSync(imagesDir)) fs.mkdirSync(imagesDir, { recursive: true });
const filename = 'ref.png';
const filePath = path.join(imagesDir, filename);
const base64Data = image.replace(/^data:image\/\w+;base64,/, '');
fs.writeFileSync(filePath, Buffer.from(base64Data, 'base64'));
const publicPath = `/api/images/${product.id}/${filename}`;
db.prepare("UPDATE products SET reference_image = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?")
.run(publicPath, req.params.id);
res.json({ reference_image: publicPath });
});
// Delete reference image for a product
router.delete('/:id/image', (req, res) => {
const product = db.prepare('SELECT * FROM products WHERE id = ?').get(req.params.id);
if (!product) return res.status(404).json({ error: 'Product not found' });
if (!product.reference_image) return res.json({ success: true });
let imagesRoot = path.join(__dirname, '..', 'images');
try {
const savePathRow = db.prepare("SELECT value FROM configs WHERE key = 'image_save_path'").get();
if (savePathRow?.value?.trim()) imagesRoot = savePathRow.value.trim();
} catch (_) {}
const filePath = path.join(imagesRoot, product.id, 'ref.png');
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
db.prepare("UPDATE products SET reference_image = '', updated_at = CURRENT_TIMESTAMP WHERE id = ?")
.run(req.params.id);
res.json({ success: true });
});
// Get product by id
router.get('/:id', (req, res) => {
const product = db.prepare('SELECT * FROM products WHERE id = ?').get(req.params.id);
if (!product) return res.status(404).json({ error: 'Product not found' });
const shop = db.prepare('SELECT shop_name FROM shops WHERE id = ?').get(product.shop_id);
res.json({ ...product, shop_name: shop?.shop_name || 'Unknown' });
});
// Create product
router.post('/', (req, res) => {
const { shop_id, title } = req.body;
if (!shop_id || !title) {
return res.status(400).json({ error: 'shop_id and title are required' });
}
const shop = db.prepare('SELECT * FROM shops WHERE id = ?').get(shop_id);
if (!shop) return res.status(404).json({ error: 'Shop not found' });
const id = uuidv4();
db.prepare('INSERT INTO products (id, shop_id, title) VALUES (?, ?, ?)').run(id, shop_id, title);
res.status(201).json(db.prepare('SELECT * FROM products WHERE id = ?').get(id));
});
// Batch create products
router.post('/batch', (req, res) => {
const { shop_id, titles } = req.body;
if (!shop_id || !Array.isArray(titles) || titles.length === 0) {
return res.status(400).json({ error: 'shop_id and titles array are required' });
}
const shop = db.prepare('SELECT * FROM shops WHERE id = ?').get(shop_id);
if (!shop) return res.status(404).json({ error: 'Shop not found' });
const insert = db.prepare('INSERT INTO products (id, shop_id, title) VALUES (?, ?, ?)');
const products = [];
const batchInsert = db.transaction((titles) => {
for (const title of titles) {
const id = uuidv4();
insert.run(id, shop_id, title);
products.push(id);
}
});
batchInsert(titles);
const created = db.prepare('SELECT * FROM products WHERE id IN (' + products.map(() => '?').join(',') + ')').all(...products);
res.status(201).json(created);
});
// Update product
router.put('/:id', (req, res) => {
const { title, status } = req.body;
const product = db.prepare('SELECT * FROM products WHERE id = ?').get(req.params.id);
if (!product) return res.status(404).json({ error: 'Product not found' });
db.prepare('UPDATE products SET title = ?, status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
.run(title || product.title, status || product.status, req.params.id);
res.json(db.prepare('SELECT * FROM products WHERE id = ?').get(req.params.id));
});
// Batch delete products (POST because DELETE with body is not reliable)
router.post('/batch-delete', (req, res) => {
const { ids } = req.body;
if (!Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({ error: 'ids array required' });
}
const placeholders = ids.map(() => '?').join(',');
const result = db.prepare('DELETE FROM products WHERE id IN (' + placeholders + ')').run(...ids);
res.json({ deleted: result.changes });
});
// Batch delete products (legacy DELETE route)
router.delete('/batch', (req, res) => {
const { ids } = req.body;
if (!Array.isArray(ids) || ids.length === 0) {
return res.status(400).json({ error: 'ids array required' });
}
const placeholders = ids.map(() => '?').join(',');
const result = db.prepare('DELETE FROM products WHERE id IN (' + placeholders + ')').run(...ids);
res.json({ deleted: result.changes });
});
// Delete product
router.delete('/:id', (req, res) => {
const result = db.prepare('DELETE FROM products WHERE id = ?').run(req.params.id);
if (result.changes === 0) return res.status(404).json({ error: 'Product not found' });
res.json({ success: true });
});
// Import products from array
router.post('/import', (req, res) => {
const { shop_id, products: importProducts } = req.body;
if (!shop_id || !Array.isArray(importProducts) || importProducts.length === 0) {
return res.status(400).json({ error: 'shop_id and products array required' });
}
const shop = db.prepare('SELECT * FROM shops WHERE id = ?').get(shop_id);
if (!shop) return res.status(404).json({ error: 'Shop not found' });
const insert = db.prepare('INSERT OR IGNORE INTO products (id, shop_id, title) VALUES (?, ?, ?)');
let imported = 0;
const batch = db.transaction(() => {
for (const p of importProducts) {
const title = typeof p === 'string' ? p : p.title;
if (!title) continue;
const id = uuidv4();
const info = insert.run(id, shop_id, title);
if (info.changes > 0) imported++;
}
});
batch();
res.json({ total: importProducts.length, imported });
});
return router;
}
module.exports = createProductRoutes;