feat: Add stale process monitor, users route, landing page, archive old scripts
- Add backend stale process monitoring API (/api/stale-processes) - Add users management route - Add frontend landing page and stale process monitor UI on /scraper-tools - Move old development scripts to backend/archive/ - Update frontend build with new features 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
190
backend/archive/test-stock-status.ts
Normal file
190
backend/archive/test-stock-status.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { firefox } from 'playwright';
|
||||
import { getRandomProxy } from './src/utils/proxyManager.js';
|
||||
|
||||
async function testStockStatus() {
|
||||
const proxy = await getRandomProxy();
|
||||
if (!proxy) {
|
||||
console.log('❌ No proxy available');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`🔐 Using proxy: ${proxy.server}`);
|
||||
|
||||
const browser = await firefox.launch({ headless: true });
|
||||
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1920, height: 1080 },
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
proxy: {
|
||||
server: proxy.server,
|
||||
username: proxy.username,
|
||||
password: proxy.password
|
||||
}
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
|
||||
// Test multiple dispensaries
|
||||
const dispensaries = [
|
||||
'AZ-Deeply-Rooted',
|
||||
'CA-Sol-Flower-Blythe',
|
||||
'CA-Cookies-Santa-Ana',
|
||||
'CO-Lemonnade-Denver'
|
||||
];
|
||||
|
||||
let totalFoundWithStock = 0;
|
||||
|
||||
for (const dispensary of dispensaries) {
|
||||
try {
|
||||
const menuUrl = `https://dutchie.com/embedded-menu/${dispensary}`;
|
||||
console.log(`\n📍 Loading menu: ${menuUrl}...`);
|
||||
|
||||
await page.goto(menuUrl, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 45000
|
||||
});
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Scroll to load more products
|
||||
console.log('📜 Scrolling to load products...');
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
// Analyze stock status text across all product cards
|
||||
const stockAnalysis = await page.evaluate(() => {
|
||||
const productCards = Array.from(document.querySelectorAll('a[href*="/product/"]'));
|
||||
console.log(`Found ${productCards.length} product cards`);
|
||||
|
||||
const stockStatuses: {
|
||||
allText: string;
|
||||
hasOutOfStock: boolean;
|
||||
hasLowStock: boolean;
|
||||
hasExpired: boolean;
|
||||
hasPreOrder: boolean;
|
||||
hasQuantity: boolean;
|
||||
stockRelatedText: string[];
|
||||
}[] = [];
|
||||
|
||||
const stockKeywords = [
|
||||
'out of stock',
|
||||
'low stock',
|
||||
'limited',
|
||||
'expired',
|
||||
'pre-order',
|
||||
'preorder',
|
||||
'call for',
|
||||
'only',
|
||||
'left',
|
||||
'available',
|
||||
'in stock',
|
||||
'unavailable'
|
||||
];
|
||||
|
||||
productCards.forEach((card) => {
|
||||
const allText = (card.textContent || '').toLowerCase();
|
||||
|
||||
// Find any stock-related text
|
||||
const foundKeywords = stockKeywords.filter(keyword =>
|
||||
allText.includes(keyword)
|
||||
);
|
||||
|
||||
if (foundKeywords.length > 0) {
|
||||
stockStatuses.push({
|
||||
allText: allText.substring(0, 200),
|
||||
hasOutOfStock: allText.includes('out of stock'),
|
||||
hasLowStock: allText.includes('low stock') || allText.includes('limited'),
|
||||
hasExpired: allText.includes('expired'),
|
||||
hasPreOrder: allText.includes('pre-order') || allText.includes('preorder'),
|
||||
hasQuantity: /only \d+ left/.test(allText) || /\d+ left/.test(allText),
|
||||
stockRelatedText: foundKeywords
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
totalProducts: productCards.length,
|
||||
productsWithStockInfo: stockStatuses.length,
|
||||
stockStatuses: stockStatuses.slice(0, 20) // Sample first 20
|
||||
};
|
||||
});
|
||||
|
||||
console.log(`\n📊 Stock Status Analysis:`);
|
||||
console.log(` Total products found: ${stockAnalysis.totalProducts}`);
|
||||
console.log(` Products with stock info: ${stockAnalysis.productsWithStockInfo}`);
|
||||
|
||||
if (stockAnalysis.productsWithStockInfo > 0) {
|
||||
console.log(`\n Stock Status Samples:`);
|
||||
|
||||
const summary = {
|
||||
outOfStock: 0,
|
||||
lowStock: 0,
|
||||
expired: 0,
|
||||
preOrder: 0,
|
||||
quantity: 0
|
||||
};
|
||||
|
||||
stockAnalysis.stockStatuses.forEach((status, i) => {
|
||||
console.log(`\n Product ${i + 1}:`);
|
||||
console.log(` Keywords found: ${status.stockRelatedText.join(', ')}`);
|
||||
console.log(` Text preview: "${status.allText.substring(0, 150)}..."`);
|
||||
|
||||
if (status.hasOutOfStock) {
|
||||
summary.outOfStock++;
|
||||
console.log(` ⚠️ OUT OF STOCK`);
|
||||
}
|
||||
if (status.hasLowStock) {
|
||||
summary.lowStock++;
|
||||
console.log(` ⚠️ LOW STOCK / LIMITED`);
|
||||
}
|
||||
if (status.hasExpired) {
|
||||
summary.expired++;
|
||||
console.log(` ⚠️ EXPIRED`);
|
||||
}
|
||||
if (status.hasPreOrder) {
|
||||
summary.preOrder++;
|
||||
console.log(` ⚠️ PRE-ORDER`);
|
||||
}
|
||||
if (status.hasQuantity) {
|
||||
summary.quantity++;
|
||||
console.log(` ⚠️ QUANTITY LIMITED`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`\n Summary (from sampled products):`);
|
||||
console.log(` Out of stock: ${summary.outOfStock}`);
|
||||
console.log(` Low stock: ${summary.lowStock}`);
|
||||
console.log(` Expired: ${summary.expired}`);
|
||||
console.log(` Pre-order: ${summary.preOrder}`);
|
||||
console.log(` Quantity limited: ${summary.quantity}`);
|
||||
} else {
|
||||
console.log(`\n ✅ No stock status indicators found (all products appear in stock)`);
|
||||
}
|
||||
|
||||
if (stockAnalysis.productsWithStockInfo > 0) {
|
||||
totalFoundWithStock += stockAnalysis.productsWithStockInfo;
|
||||
// Take a screenshot if we found stock info
|
||||
await page.screenshot({ path: `/tmp/stock-${dispensary}.png`, fullPage: true });
|
||||
console.log(`\n📸 Screenshot saved to /tmp/stock-${dispensary}.png`);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error(` ❌ Error loading ${dispensary}:`, error.message);
|
||||
}
|
||||
|
||||
// Wait between dispensaries
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
console.log(`\n${'='.repeat(60)}`);
|
||||
console.log(`✅ SEARCH COMPLETE`);
|
||||
console.log(` Dispensaries checked: ${dispensaries.length}`);
|
||||
console.log(` Total products with stock info found: ${totalFoundWithStock}`);
|
||||
console.log(`${'='.repeat(60)}\n`);
|
||||
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
testStockStatus();
|
||||
Reference in New Issue
Block a user