Initial commit: Inventory Barcode System
This commit is contained in:
776
routes/products.js
Normal file
776
routes/products.js
Normal file
@ -0,0 +1,776 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const Product = require('../models/Product');
|
||||
const ExcelImportService = require('../services/ExcelImportService');
|
||||
const logger = require('../utils/logger');
|
||||
const {
|
||||
ValidationError,
|
||||
NotFoundError,
|
||||
ConflictError,
|
||||
asyncHandler
|
||||
} = require('../middleware/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* GET /api/products/test
|
||||
* Test endpoint to verify API is working - MUST BE FIRST to avoid /:id conflict
|
||||
*/
|
||||
router.get('/test', (req, res) => {
|
||||
console.log('Test endpoint accessed from:', req.ip, req.get('User-Agent'));
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Products API is working',
|
||||
timestamp: new Date().toISOString(),
|
||||
server: {
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
uptime: process.uptime()
|
||||
},
|
||||
request: {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
baseUrl: req.baseUrl,
|
||||
originalUrl: req.originalUrl,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
},
|
||||
endpoints: [
|
||||
'GET /api/products',
|
||||
'GET /api/products/:id',
|
||||
'POST /api/products',
|
||||
'PUT /api/products/:id',
|
||||
'DELETE /api/products/:id',
|
||||
'POST /api/products/import/excel/preview',
|
||||
'POST /api/products/import/excel',
|
||||
'POST /api/products/bulk'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Configure multer for file uploads
|
||||
const upload = multer({
|
||||
storage: multer.memoryStorage(),
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024, // 10MB limit
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Accept Excel files
|
||||
const allowedMimes = [
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
||||
'application/vnd.ms-excel', // .xls
|
||||
];
|
||||
|
||||
if (allowedMimes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only Excel files (.xlsx, .xls) are allowed'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/products
|
||||
* Get all products with optional filtering
|
||||
*/
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
logger.info('Retrieving products', {
|
||||
requestId: req.requestId,
|
||||
filters: req.query
|
||||
});
|
||||
|
||||
const filters = {};
|
||||
|
||||
// Extract and validate query parameters for filtering
|
||||
if (req.query.category) {
|
||||
if (typeof req.query.category !== 'string' || req.query.category.length > 100) {
|
||||
throw new ValidationError('Invalid category filter');
|
||||
}
|
||||
filters.category = req.query.category.trim();
|
||||
}
|
||||
|
||||
if (req.query.name) {
|
||||
if (typeof req.query.name !== 'string' || req.query.name.length > 200) {
|
||||
throw new ValidationError('Invalid name filter');
|
||||
}
|
||||
filters.name = req.query.name.trim();
|
||||
}
|
||||
|
||||
const products = await Product.findAll(filters);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
logger.info('Products retrieved successfully', {
|
||||
requestId: req.requestId,
|
||||
count: products.length,
|
||||
duration: `${duration}ms`,
|
||||
filters
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: products.map(product => product.toJSON()),
|
||||
count: products.length,
|
||||
filters: Object.keys(filters).length > 0 ? filters : undefined
|
||||
});
|
||||
} catch (error) {
|
||||
logger.logError(error, {
|
||||
operation: 'get_products',
|
||||
requestId: req.requestId,
|
||||
filters: req.query
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/products/api-test
|
||||
* Test endpoint to verify API is working
|
||||
*/
|
||||
router.get('/api-test', (req, res) => {
|
||||
console.log('Test endpoint accessed from:', req.ip, req.get('User-Agent'));
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Products API is working',
|
||||
timestamp: new Date().toISOString(),
|
||||
server: {
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
uptime: process.uptime()
|
||||
},
|
||||
request: {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
baseUrl: req.baseUrl,
|
||||
originalUrl: req.originalUrl,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
},
|
||||
endpoints: [
|
||||
'GET /api/products',
|
||||
'GET /api/products/:id',
|
||||
'POST /api/products',
|
||||
'PUT /api/products/:id',
|
||||
'DELETE /api/products/:id',
|
||||
'POST /api/products/import/excel/preview',
|
||||
'POST /api/products/import/excel',
|
||||
'POST /api/products/bulk'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/products/:id
|
||||
* Get a specific product by ID
|
||||
*/
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const productId = parseInt(req.params.id);
|
||||
|
||||
if (isNaN(productId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid product ID',
|
||||
message: 'Product ID must be a valid number'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await Product.findById(productId);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Product not found',
|
||||
message: `Product with ID ${productId} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: product.toJSON()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve product',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/products/barcode/:barcode
|
||||
* Get a product by barcode
|
||||
*/
|
||||
router.get('/barcode/:barcode', async (req, res) => {
|
||||
try {
|
||||
const barcode = decodeURIComponent(req.params.barcode);
|
||||
|
||||
if (!barcode || barcode.trim() === '' || barcode.trim() === ' ') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid barcode',
|
||||
message: 'Barcode cannot be empty'
|
||||
});
|
||||
}
|
||||
|
||||
const product = await Product.findByBarcode(barcode);
|
||||
|
||||
if (!product) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Product not found',
|
||||
message: `Product with barcode ${barcode} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: product.toJSON()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to retrieve product',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/products
|
||||
* Create a new product
|
||||
*/
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const productData = req.body;
|
||||
|
||||
// Create new product instance
|
||||
const product = new Product(productData);
|
||||
|
||||
// Validate the product
|
||||
const validation = product.validate();
|
||||
if (!validation.isValid) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation failed',
|
||||
message: 'Product data is invalid',
|
||||
details: validation.errors
|
||||
});
|
||||
}
|
||||
|
||||
// Save the product
|
||||
await product.save();
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: product.toJSON(),
|
||||
message: 'Product created successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('already exists')) {
|
||||
res.status(409).json({
|
||||
success: false,
|
||||
error: 'Conflict',
|
||||
message: error.message
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to create product',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /api/products/:id
|
||||
* Update an existing product
|
||||
*/
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const productId = parseInt(req.params.id);
|
||||
|
||||
if (isNaN(productId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid product ID',
|
||||
message: 'Product ID must be a valid number'
|
||||
});
|
||||
}
|
||||
|
||||
// Find existing product
|
||||
const existingProduct = await Product.findById(productId);
|
||||
if (!existingProduct) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Product not found',
|
||||
message: `Product with ID ${productId} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
// Update product data
|
||||
const updatedData = { ...existingProduct.toJSON(), ...req.body, id: productId };
|
||||
const product = new Product(updatedData);
|
||||
|
||||
// Validate the updated product
|
||||
const validation = product.validate();
|
||||
if (!validation.isValid) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation failed',
|
||||
message: 'Updated product data is invalid',
|
||||
details: validation.errors
|
||||
});
|
||||
}
|
||||
|
||||
// Save the updated product
|
||||
await product.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: product.toJSON(),
|
||||
message: 'Product updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('already exists')) {
|
||||
res.status(409).json({
|
||||
success: false,
|
||||
error: 'Conflict',
|
||||
message: error.message
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to update product',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/products/:id
|
||||
* Delete a product
|
||||
*/
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const productId = parseInt(req.params.id);
|
||||
|
||||
if (isNaN(productId)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid product ID',
|
||||
message: 'Product ID must be a valid number'
|
||||
});
|
||||
}
|
||||
|
||||
const deleted = await Product.deleteById(productId);
|
||||
|
||||
if (!deleted) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'Product not found',
|
||||
message: `Product with ID ${productId} does not exist`
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Product deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to delete product',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/products/import/excel
|
||||
* Import products from Excel file
|
||||
*/
|
||||
router.post('/import/excel', upload.single('file'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'No file uploaded',
|
||||
message: 'Please upload an Excel file'
|
||||
});
|
||||
}
|
||||
|
||||
const options = {
|
||||
filename: req.file.originalname,
|
||||
checkExisting: req.body.checkExisting !== 'false',
|
||||
duplicateStrategy: req.body.duplicateStrategy || 'skip',
|
||||
importToDatabase: req.body.importToDatabase !== 'false',
|
||||
forceImport: req.body.forceImport === 'true',
|
||||
updatedBy: req.body.updatedBy || 'api-user'
|
||||
};
|
||||
|
||||
const excelImportService = new ExcelImportService();
|
||||
const results = await excelImportService.processImport(req.file.buffer, options);
|
||||
|
||||
if (!results.success) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Import failed',
|
||||
message: 'Failed to process Excel file',
|
||||
details: results.errors
|
||||
});
|
||||
}
|
||||
|
||||
// Determine response status based on validation results
|
||||
let statusCode = 200;
|
||||
if (results.validationResults && !results.validationResults.isValid) {
|
||||
statusCode = 422; // Unprocessable Entity
|
||||
}
|
||||
|
||||
res.status(statusCode).json({
|
||||
success: true,
|
||||
data: {
|
||||
parseResults: results.parseResults,
|
||||
validationResults: results.validationResults,
|
||||
importResults: results.importResults
|
||||
},
|
||||
message: 'Excel file processed successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('Only Excel files')) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid file type',
|
||||
message: error.message
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Import failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/products/import/excel/preview
|
||||
* Preview Excel file import without saving to database
|
||||
*/
|
||||
/**
|
||||
* Direct import preview endpoint that doesn't rely on ExcelImportService
|
||||
* This is a simplified version for testing
|
||||
*/
|
||||
router.post('/direct-import-preview', upload.single('file'), async (req, res) => {
|
||||
console.log('Direct import preview endpoint hit:', {
|
||||
hasFile: !!req.file,
|
||||
filename: req.file?.originalname,
|
||||
size: req.file?.size,
|
||||
mimetype: req.file?.mimetype
|
||||
});
|
||||
|
||||
try {
|
||||
if (!req.file) {
|
||||
console.log('No file uploaded');
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'No file uploaded',
|
||||
message: 'Please upload an Excel file'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Processing file:', req.file.originalname);
|
||||
|
||||
// Simple mock response for testing
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
preview: {
|
||||
totalRows: 3,
|
||||
validProducts: 2,
|
||||
invalidProducts: 1,
|
||||
duplicateProducts: 0,
|
||||
existingProducts: 0,
|
||||
sampleProducts: [
|
||||
{ product_code: 'TEST001', description: 'Test Product 1', quantity: 10, isValid: true },
|
||||
{ product_code: 'TEST002', description: 'Test Product 2', quantity: 20, isValid: true },
|
||||
{ product_code: '', description: 'Invalid Product', quantity: 0, isValid: false, validationErrors: [{ message: 'Missing product code' }] }
|
||||
]
|
||||
},
|
||||
validationResults: {
|
||||
isValid: false,
|
||||
errors: [
|
||||
{ row: 3, message: 'Missing product code' }
|
||||
],
|
||||
statistics: {
|
||||
validProducts: 2,
|
||||
invalidProducts: 1,
|
||||
duplicateProducts: 0,
|
||||
existingProducts: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
message: 'Excel file preview generated successfully (direct endpoint)'
|
||||
};
|
||||
|
||||
console.log('Sending direct import response');
|
||||
res.json(mockResponse);
|
||||
} catch (error) {
|
||||
console.error('Direct import preview error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Import preview failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Regular import preview endpoint
|
||||
*/
|
||||
router.post('/import/excel/preview', upload.single('file'), async (req, res) => {
|
||||
console.log('Import preview endpoint hit:', {
|
||||
hasFile: !!req.file,
|
||||
filename: req.file?.originalname,
|
||||
size: req.file?.size,
|
||||
mimetype: req.file?.mimetype
|
||||
});
|
||||
|
||||
try {
|
||||
if (!req.file) {
|
||||
console.log('No file uploaded');
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'No file uploaded',
|
||||
message: 'Please upload an Excel file'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Processing file:', req.file.originalname);
|
||||
|
||||
// For now, return a simple mock response to test the endpoint
|
||||
const mockResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
preview: {
|
||||
totalRows: 3,
|
||||
validProducts: 2,
|
||||
invalidProducts: 1,
|
||||
duplicateProducts: 0,
|
||||
existingProducts: 0,
|
||||
sampleProducts: [
|
||||
{ product_code: 'TEST001', description: 'Test Product 1', quantity: 10, isValid: true },
|
||||
{ product_code: 'TEST002', description: 'Test Product 2', quantity: 20, isValid: true },
|
||||
{ product_code: '', description: 'Invalid Product', quantity: 0, isValid: false, validationErrors: [{ message: 'Missing product code' }] }
|
||||
]
|
||||
},
|
||||
validationResults: {
|
||||
isValid: false,
|
||||
errors: [
|
||||
{ row: 3, message: 'Missing product code' }
|
||||
],
|
||||
statistics: {
|
||||
validProducts: 2,
|
||||
invalidProducts: 1,
|
||||
duplicateProducts: 0,
|
||||
existingProducts: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
message: 'Excel file preview generated successfully (mock data)'
|
||||
};
|
||||
|
||||
console.log('Sending mock response');
|
||||
res.json(mockResponse);
|
||||
|
||||
/* Original code - commented out for testing
|
||||
const options = {
|
||||
filename: req.file.originalname,
|
||||
checkExisting: req.body.checkExisting !== 'false',
|
||||
importToDatabase: false // Never import for preview
|
||||
};
|
||||
|
||||
const excelImportService = new ExcelImportService();
|
||||
const results = await excelImportService.processImport(req.file.buffer, options);
|
||||
|
||||
if (!results.success) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Preview failed',
|
||||
message: 'Failed to process Excel file',
|
||||
details: results.errors
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
parseResults: results.parseResults,
|
||||
validationResults: results.validationResults,
|
||||
preview: {
|
||||
totalRows: results.parseResults.data.totalRows,
|
||||
validProducts: results.validationResults.statistics.validProducts,
|
||||
invalidProducts: results.validationResults.statistics.invalidProducts,
|
||||
duplicateProducts: results.validationResults.statistics.duplicateProducts,
|
||||
existingProducts: results.validationResults.statistics.existingProducts,
|
||||
sampleProducts: results.validationResults.validatedProducts.slice(0, 5) // First 5 for preview
|
||||
}
|
||||
},
|
||||
message: 'Excel file preview generated successfully'
|
||||
});
|
||||
*/
|
||||
} catch (error) {
|
||||
console.error('Import preview error:', error);
|
||||
if (error.message.includes('Only Excel files')) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid file type',
|
||||
message: error.message
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Preview failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/products/bulk
|
||||
* Create multiple products in bulk
|
||||
*/
|
||||
router.post('/bulk', async (req, res) => {
|
||||
try {
|
||||
const { products } = req.body;
|
||||
|
||||
if (!Array.isArray(products) || products.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
message: 'Products array is required and cannot be empty'
|
||||
});
|
||||
}
|
||||
|
||||
const results = {
|
||||
success: true,
|
||||
created: 0,
|
||||
failed: 0,
|
||||
errors: [],
|
||||
createdProducts: []
|
||||
};
|
||||
|
||||
// Process each product
|
||||
for (let i = 0; i < products.length; i++) {
|
||||
try {
|
||||
const productData = products[i];
|
||||
const product = new Product(productData);
|
||||
|
||||
// Validate the product
|
||||
const validation = product.validate();
|
||||
if (!validation.isValid) {
|
||||
results.failed++;
|
||||
results.errors.push({
|
||||
index: i,
|
||||
productData: productData,
|
||||
errors: validation.errors
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the product
|
||||
await product.save();
|
||||
results.created++;
|
||||
results.createdProducts.push(product.toJSON());
|
||||
|
||||
} catch (error) {
|
||||
results.failed++;
|
||||
results.errors.push({
|
||||
index: i,
|
||||
productData: products[i],
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Determine response status
|
||||
let statusCode = 200;
|
||||
if (results.failed > 0 && results.created === 0) {
|
||||
statusCode = 400; // All failed
|
||||
results.success = false;
|
||||
} else if (results.failed > 0) {
|
||||
statusCode = 207; // Partial success
|
||||
} else {
|
||||
statusCode = 201; // All created
|
||||
}
|
||||
|
||||
res.status(statusCode).json({
|
||||
...results,
|
||||
message: `Bulk operation completed: ${results.created} created, ${results.failed} failed`
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Bulk operation failed',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/products/categories
|
||||
* Get all unique product categories
|
||||
*/
|
||||
router.get('/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Product.getCategories();
|
||||
|
||||
res.json(categories);
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch categories',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Catch-all route for debugging
|
||||
*/
|
||||
router.all('*', (req, res) => {
|
||||
console.log('Unmatched products route:', {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
originalUrl: req.originalUrl,
|
||||
baseUrl: req.baseUrl
|
||||
});
|
||||
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Route not found',
|
||||
message: `Route ${req.method} ${req.originalUrl} not found in products router`,
|
||||
availableRoutes: [
|
||||
'GET /api/products',
|
||||
'GET /api/products/api-test',
|
||||
'POST /api/products/import/excel/preview',
|
||||
'POST /api/products/import/excel'
|
||||
],
|
||||
debug: {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
originalUrl: req.originalUrl,
|
||||
baseUrl: req.baseUrl
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user