diff --git a/backend/src/routes/dashboard.ts b/backend/src/routes/dashboard.ts index 7fe39434..5171ffe6 100755 --- a/backend/src/routes/dashboard.ts +++ b/backend/src/routes/dashboard.ts @@ -5,6 +5,9 @@ import { pool } from '../db/pool'; const router = Router(); router.use(authMiddleware); +// In-memory cache for activity endpoint (1 minute TTL) +let activityCache: { data: any; expiresAt: number } | null = null; + // Get dashboard stats - uses consolidated dutchie-az DB // OPTIMIZED: Combined 4 sequential queries into 1 using CTEs router.get('/stats', async (req, res) => { @@ -88,26 +91,30 @@ router.get('/stats', async (req, res) => { }); // Get recent activity - from consolidated dutchie-az DB -// OPTIMIZED: Use pre-computed counts and indexed queries +// OPTIMIZED: Uses denormalized product_count column + 1 minute cache router.get('/activity', async (req, res) => { try { - const { limit = 10 } = req.query; // Reduced default limit - const limitNum = Math.min(parseInt(limit as string) || 10, 20); // Cap at 20 + // Check cache first (1 minute TTL) + if (activityCache && activityCache.expiresAt > Date.now()) { + return res.json(activityCache.data); + } - // Recent crawls - use subquery for product count (faster than JOIN+GROUP BY) - // Uses index on last_crawl_at + const { limit = 10 } = req.query; + const limitNum = Math.min(parseInt(limit as string) || 10, 20); + + // Recent crawls - use denormalized product_count column (no correlated subquery!) const scrapesResult = await pool.query(` SELECT d.name, d.last_crawl_at as last_scraped_at, - (SELECT COUNT(*) FROM store_products sp WHERE sp.dispensary_id = d.id) as product_count + COALESCE(d.product_count, 0) as product_count FROM dispensaries d WHERE d.last_crawl_at IS NOT NULL ORDER BY d.last_crawl_at DESC LIMIT $1 `, [limitNum]); - // Recent products - uses index on created_at + // Recent products - uses index on created_at (idx_store_products_created_at) const productsResult = await pool.query(` SELECT p.name_raw as name, @@ -123,10 +130,15 @@ router.get('/activity', async (req, res) => { LIMIT $1 `, [limitNum]); - res.json({ + const data = { recent_scrapes: scrapesResult.rows, recent_products: productsResult.rows - }); + }; + + // Cache for 1 minute + activityCache = { data, expiresAt: Date.now() + 60 * 1000 }; + + res.json(data); } catch (error) { console.error('Error fetching dashboard activity:', error); res.status(500).json({ error: 'Failed to fetch dashboard activity' });