mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-05-07 23:02:40 +02:00
Add stock status history tracking and timeline visualization
- Create stock_status_history table to track status changes over time - Add stockStatusHistoryQueries with getByProductId, recordChange, getStats - Update scheduler to record status changes - Update product creation and manual refresh to record initial/changed status - Add GET /products/:id/stock-history API endpoint - Create StockTimeline component with: - Visual timeline bar showing in-stock (green) vs out-of-stock (red) - Availability percentage - Outage count and duration stats - Integrate timeline into ProductDetail page Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4928d6b9d3
commit
6c2aece1e8
8 changed files with 528 additions and 6 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { Router, Response } from 'express';
|
||||
import { AuthRequest, authMiddleware } from '../middleware/auth';
|
||||
import { productQueries, priceHistoryQueries } from '../models';
|
||||
import { productQueries, priceHistoryQueries, stockStatusHistoryQueries } from '../models';
|
||||
import { scrapeProduct } from '../services/scraper';
|
||||
|
||||
const router = Router();
|
||||
|
|
@ -65,8 +65,11 @@ router.post('/:productId/refresh', async (req: AuthRequest, res: Response) => {
|
|||
// Scrape product data including price and stock status
|
||||
const scrapedData = await scrapeProduct(product.url);
|
||||
|
||||
// Update stock status
|
||||
await productQueries.updateStockStatus(productId, scrapedData.stockStatus);
|
||||
// Update stock status and record change if different
|
||||
if (scrapedData.stockStatus !== product.stock_status) {
|
||||
await productQueries.updateStockStatus(productId, scrapedData.stockStatus);
|
||||
await stockStatusHistoryQueries.recordChange(productId, scrapedData.stockStatus);
|
||||
}
|
||||
|
||||
// Record new price if available
|
||||
let newPrice = null;
|
||||
|
|
@ -94,4 +97,38 @@ router.post('/:productId/refresh', async (req: AuthRequest, res: Response) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Get stock status history for a product
|
||||
router.get('/:productId/stock-history', async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const userId = req.userId!;
|
||||
const productId = parseInt(req.params.productId, 10);
|
||||
|
||||
if (isNaN(productId)) {
|
||||
res.status(400).json({ error: 'Invalid product ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify product belongs to user
|
||||
const product = await productQueries.findById(productId, userId);
|
||||
if (!product) {
|
||||
res.status(404).json({ error: 'Product not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get optional days filter from query (default 30 days)
|
||||
const days = req.query.days ? parseInt(req.query.days as string, 10) : 30;
|
||||
|
||||
const stockHistory = await stockStatusHistoryQueries.getByProductId(productId, days);
|
||||
const stats = await stockStatusHistoryQueries.getStats(productId, days);
|
||||
|
||||
res.json({
|
||||
history: stockHistory,
|
||||
stats,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching stock status history:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch stock status history' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue