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:
Kelly
2025-12-05 04:07:31 -07:00
parent d2d44d2aeb
commit d91c55a344
3115 changed files with 5755 additions and 719 deletions

View 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();