← back to Designer Wallcoverings

DW-Agents/dw-agents/amazon-data-cache.ts

322 lines

/**
 * Amazon Data Cache System
 *
 * Stores ALL Amazon data locally so it's available even when Amazon is down:
 * - Product details
 * - Price history
 * - Order history
 * - Product images and descriptions
 * - Search results
 */

import * as fs from 'fs';
import * as path from 'path';

const CACHE_DIR = '/root/Projects/Designer-Wallcoverings/DW-Agents/data/amazon-cache';
const ORDERS_CACHE = path.join(CACHE_DIR, 'orders.json');
const PRODUCTS_CACHE = path.join(CACHE_DIR, 'products.json');
const PRICE_HISTORY_CACHE = path.join(CACHE_DIR, 'price-history.json');
const SEARCH_CACHE = path.join(CACHE_DIR, 'searches.json');

// Ensure cache directory exists
if (!fs.existsSync(CACHE_DIR)) {
  fs.mkdirSync(CACHE_DIR, { recursive: true });
  console.log('✅ Created Amazon cache directory');
}

export interface CachedOrder {
  orderId: string;
  timestamp: Date;
  item: string;
  price: number;
  vendor: string;
  status: string;
  tracking?: string;
  productUrl?: string;
  productImage?: string;
  asin?: string;
}

export interface CachedProduct {
  asin: string;
  title: string;
  description: string;
  currentPrice: number;
  imageUrl: string;
  productUrl: string;
  availability: string;
  rating?: number;
  reviewCount?: number;
  lastUpdated: Date;
  category?: string;
  brand?: string;
}

export interface PriceHistoryEntry {
  asin: string;
  productTitle: string;
  timestamp: Date;
  price: number;
  vendor: string;
}

export interface SearchCacheEntry {
  query: string;
  timestamp: Date;
  results: CachedProduct[];
}

/**
 * Save order to cache
 */
export function cacheOrder(order: CachedOrder): void {
  try {
    let orders: CachedOrder[] = [];

    if (fs.existsSync(ORDERS_CACHE)) {
      const data = fs.readFileSync(ORDERS_CACHE, 'utf-8');
      orders = JSON.parse(data);
    }

    // Check if order already exists
    const existingIndex = orders.findIndex(o => o.orderId === order.orderId);
    if (existingIndex >= 0) {
      orders[existingIndex] = order;
      console.log(`📦 Updated cached order: ${order.orderId}`);
    } else {
      orders.push(order);
      console.log(`📦 Cached new order: ${order.orderId}`);
    }

    fs.writeFileSync(ORDERS_CACHE, JSON.stringify(orders, null, 2));
  } catch (error) {
    console.error('❌ Error caching order:', error);
  }
}

/**
 * Save product to cache
 */
export function cacheProduct(product: CachedProduct): void {
  try {
    let products: { [asin: string]: CachedProduct } = {};

    if (fs.existsSync(PRODUCTS_CACHE)) {
      const data = fs.readFileSync(PRODUCTS_CACHE, 'utf-8');
      products = JSON.parse(data);
    }

    products[product.asin] = product;
    fs.writeFileSync(PRODUCTS_CACHE, JSON.stringify(products, null, 2));

    console.log(`📦 Cached product: ${product.asin} - ${product.title.substring(0, 50)}...`);

    // Also save to price history
    savePriceHistory({
      asin: product.asin,
      productTitle: product.title,
      timestamp: new Date(),
      price: product.currentPrice,
      vendor: 'Amazon'
    });
  } catch (error) {
    console.error('❌ Error caching product:', error);
  }
}

/**
 * Save price history entry
 */
export function savePriceHistory(entry: PriceHistoryEntry): void {
  try {
    let history: PriceHistoryEntry[] = [];

    if (fs.existsSync(PRICE_HISTORY_CACHE)) {
      const data = fs.readFileSync(PRICE_HISTORY_CACHE, 'utf-8');
      history = JSON.parse(data);
    }

    history.push(entry);

    // Keep last 10,000 entries
    if (history.length > 10000) {
      history = history.slice(-10000);
    }

    fs.writeFileSync(PRICE_HISTORY_CACHE, JSON.stringify(history, null, 2));
  } catch (error) {
    console.error('❌ Error saving price history:', error);
  }
}

/**
 * Cache search results
 */
export function cacheSearch(query: string, results: CachedProduct[]): void {
  try {
    let searches: SearchCacheEntry[] = [];

    if (fs.existsSync(SEARCH_CACHE)) {
      const data = fs.readFileSync(SEARCH_CACHE, 'utf-8');
      searches = JSON.parse(data);
    }

    // Remove old entry for same query
    searches = searches.filter(s => s.query.toLowerCase() !== query.toLowerCase());

    searches.push({
      query,
      timestamp: new Date(),
      results
    });

    // Keep last 100 searches
    if (searches.length > 100) {
      searches = searches.slice(-100);
    }

    fs.writeFileSync(SEARCH_CACHE, JSON.stringify(searches, null, 2));
    console.log(`🔍 Cached search: "${query}" (${results.length} results)`);
  } catch (error) {
    console.error('❌ Error caching search:', error);
  }
}

/**
 * Get cached orders
 */
export function getCachedOrders(): CachedOrder[] {
  try {
    if (fs.existsSync(ORDERS_CACHE)) {
      const data = fs.readFileSync(ORDERS_CACHE, 'utf-8');
      return JSON.parse(data);
    }
  } catch (error) {
    console.error('❌ Error reading cached orders:', error);
  }
  return [];
}

/**
 * Get cached product by ASIN
 */
export function getCachedProduct(asin: string): CachedProduct | null {
  try {
    if (fs.existsSync(PRODUCTS_CACHE)) {
      const data = fs.readFileSync(PRODUCTS_CACHE, 'utf-8');
      const products = JSON.parse(data);
      return products[asin] || null;
    }
  } catch (error) {
    console.error('❌ Error reading cached product:', error);
  }
  return null;
}

/**
 * Get all cached products
 */
export function getAllCachedProducts(): CachedProduct[] {
  try {
    if (fs.existsSync(PRODUCTS_CACHE)) {
      const data = fs.readFileSync(PRODUCTS_CACHE, 'utf-8');
      const products = JSON.parse(data);
      return Object.values(products);
    }
  } catch (error) {
    console.error('❌ Error reading cached products:', error);
  }
  return [];
}

/**
 * Get price history for product
 */
export function getPriceHistory(asin: string): PriceHistoryEntry[] {
  try {
    if (fs.existsSync(PRICE_HISTORY_CACHE)) {
      const data = fs.readFileSync(PRICE_HISTORY_CACHE, 'utf-8');
      const history: PriceHistoryEntry[] = JSON.parse(data);
      return history.filter(h => h.asin === asin);
    }
  } catch (error) {
    console.error('❌ Error reading price history:', error);
  }
  return [];
}

/**
 * Search cached products
 */
export function searchCachedProducts(query: string): CachedProduct[] {
  try {
    const allProducts = getAllCachedProducts();
    const lowerQuery = query.toLowerCase();

    return allProducts.filter(p =>
      p.title.toLowerCase().includes(lowerQuery) ||
      p.description?.toLowerCase().includes(lowerQuery) ||
      p.brand?.toLowerCase().includes(lowerQuery) ||
      p.category?.toLowerCase().includes(lowerQuery)
    );
  } catch (error) {
    console.error('❌ Error searching cached products:', error);
  }
  return [];
}

/**
 * Get cached search
 */
export function getCachedSearch(query: string): SearchCacheEntry | null {
  try {
    if (fs.existsSync(SEARCH_CACHE)) {
      const data = fs.readFileSync(SEARCH_CACHE, 'utf-8');
      const searches: SearchCacheEntry[] = JSON.parse(data);

      const found = searches.find(s =>
        s.query.toLowerCase() === query.toLowerCase()
      );

      if (found) {
        // Check if cache is less than 24 hours old
        const age = Date.now() - new Date(found.timestamp).getTime();
        if (age < 24 * 60 * 60 * 1000) {
          return found;
        }
      }
    }
  } catch (error) {
    console.error('❌ Error reading cached search:', error);
  }
  return null;
}

/**
 * Get cache statistics
 */
export function getCacheStats() {
  const orders = getCachedOrders();
  const products = getAllCachedProducts();

  let priceHistoryCount = 0;
  if (fs.existsSync(PRICE_HISTORY_CACHE)) {
    const data = JSON.parse(fs.readFileSync(PRICE_HISTORY_CACHE, 'utf-8'));
    priceHistoryCount = data.length;
  }

  let searchCount = 0;
  if (fs.existsSync(SEARCH_CACHE)) {
    const data = JSON.parse(fs.readFileSync(SEARCH_CACHE, 'utf-8'));
    searchCount = data.length;
  }

  return {
    totalOrders: orders.length,
    totalProducts: products.length,
    priceHistoryEntries: priceHistoryCount,
    cachedSearches: searchCount,
    cacheDirectory: CACHE_DIR
  };
}