Skip to main content
Glama
leads.js9.94 kB
const { logger } = require('../utils/logger'); const { ApiError } = require('../utils/errors'); async function makeLeadsApiRequest(endpoint, options = {}) { const { method = 'GET', headers = {}, timeout = 30000 } = options; const url = `https://api.dealerk.it/v2${endpoint}`; try { // Log the API request using the same format as StockSpark API logger.apiRequest(method, url, null); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); const response = await fetch(url, { method, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', ...headers }, signal: controller.signal }); clearTimeout(timeoutId); // Log the API response using the same format as StockSpark API logger.apiResponse(response.status, response.statusText, url); if (!response.ok) { const errorText = await response.text(); logger.error(`Leads API error: ${response.status} ${response.statusText} - ${errorText}`, { endpoint, status: response.status, statusText: response.statusText, response: errorText }); throw new ApiError( `Leads API request failed: ${response.statusText}`, response.status, { endpoint, response: errorText } ); } const data = await response.json(); // Handle Dealer.K API response structure - leads are nested in response.response let leads; if (data && data.response && Array.isArray(data.response)) { leads = data.response; logger.info(`Leads API returned ${leads.length} leads from nested response structure`); } else if (Array.isArray(data)) { leads = data; logger.info(`Leads API returned ${leads.length} leads from direct array`); } else { logger.warn('Unexpected leads API response format', { responseStructure: typeof data, hasResponse: data && typeof data.response !== 'undefined', responseType: data && data.response ? typeof data.response : 'undefined' }); leads = []; } return leads; } catch (error) { if (error.name === 'AbortError') { logger.error(`Leads API request timeout: ${url}`); throw new ApiError('Leads API request timed out', 408, { endpoint }); } if (error instanceof ApiError) { throw error; } logger.error(`Leads API request failed: ${error.message}`, error); throw new ApiError( 'Failed to connect to leads API', 500, { endpoint, originalError: error.message } ); } } async function getLeads(params = {}) { const apiKey = process.env.STOCKSPARK_API_KEY; if (!apiKey) { throw new ApiError( 'STOCKSPARK_API_KEY environment variable is required for leads API access', 401, { required_env: 'STOCKSPARK_API_KEY' } ); } const { dateFrom, dateTo, format = 'json' } = params; // Validate date format if provided if (dateFrom && !/^\d{4}-\d{2}-\d{2}$/.test(dateFrom)) { throw new ApiError('dateFrom must be in YYYY-MM-DD format', 400, { dateFrom }); } if (dateTo && !/^\d{4}-\d{2}-\d{2}$/.test(dateTo)) { throw new ApiError('dateTo must be in YYYY-MM-DD format', 400, { dateTo }); } // Build query parameters const queryParams = new URLSearchParams(); if (dateFrom) queryParams.append('dateFrom', dateFrom); if (dateTo) queryParams.append('dateTo', dateTo); if (format !== 'json') queryParams.append('format', format); const endpoint = `/${apiKey}/lead/list${queryParams.toString() ? '?' + queryParams.toString() : ''}`; try { logger.info(`Fetching leads from Dealer.K API${dateFrom ? ` from ${dateFrom}` : ''}${dateTo ? ` to ${dateTo}` : ''}`, { dateFrom, dateTo, format, apiEndpoint: endpoint }); const response = await makeLeadsApiRequest(endpoint); // Log detailed lead statistics for monitoring if (Array.isArray(response)) { logger.info(`Successfully retrieved ${response.length} leads from Dealer.K API`, { leadCount: response.length, dateRange: { from: dateFrom, to: dateTo }, format }); } else if (response && typeof response === 'object') { logger.info('Retrieved leads data object from Dealer.K API', { responseType: typeof response, dateRange: { from: dateFrom, to: dateTo }, format }); } else { logger.warn('Unexpected leads API response format', { responseType: typeof response, response }); } return response; } catch (error) { logger.error('Failed to retrieve leads', error); throw error; } } async function getLeadsForVehicle(vehicleId, params = {}, preloadedLeads = null) { if (!vehicleId) { throw new ApiError('Vehicle ID is required', 400, { vehicleId }); } try { logger.info(`Filtering leads for specific vehicle ${vehicleId}`, { vehicleId, dateFilters: params, usingPreloadedLeads: !!preloadedLeads }); let allLeads; if (preloadedLeads && Array.isArray(preloadedLeads)) { // Use preloaded leads to avoid redundant API call logger.info(`Using preloaded leads data to avoid redundant API call`, { preloadedCount: preloadedLeads.length, vehicleId }); allLeads = preloadedLeads; } else { // Get all leads from API (the API doesn't seem to support vehicle-specific filtering) logger.info(`Fetching leads from API for vehicle ${vehicleId} filtering`); allLeads = await getLeads(params); } if (!Array.isArray(allLeads)) { logger.warn('Unexpected leads API response format for vehicle filtering', { vehicleId, response: allLeads }); return []; } logger.debug(`Filtering ${allLeads.length} total leads for vehicle ${vehicleId}`); // Filter leads for specific vehicle // Based on actual API response structure: vehicle_id is the field name const vehicleLeads = allLeads.filter(lead => { return lead.vehicle_id === vehicleId || lead.vehicleId === vehicleId || lead.stockNumber === vehicleId || lead.stock_number === vehicleId; }); logger.info(`Found ${vehicleLeads.length} leads for vehicle ${vehicleId}`, { vehicleId, leadCount: vehicleLeads.length, totalLeadsSearched: allLeads.length, filterCriteria: ['vehicleId', 'vehicle_id', 'stockNumber', 'stock_number'], apiCallAvoided: !!preloadedLeads }); return vehicleLeads; } catch (error) { logger.error(`Failed to get leads for vehicle ${vehicleId}`, error); throw error; } } async function getLeadsTrends(params = {}) { const { timeframe = 'month', groupBy = 'day' } = params; try { // Calculate date range based on timeframe const now = new Date(); let dateFrom; switch (timeframe) { case 'week': dateFrom = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); break; case 'month': dateFrom = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); break; case 'quarter': dateFrom = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000); break; default: dateFrom = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); } const dateFromStr = dateFrom.toISOString().split('T')[0]; const dateToStr = now.toISOString().split('T')[0]; logger.info(`Analyzing lead trends for ${timeframe} timeframe`, { timeframe, groupBy, dateRange: { from: dateFromStr, to: dateToStr } }); const leads = await getLeads({ dateFrom: dateFromStr, dateTo: dateToStr }); if (!Array.isArray(leads)) { return { trends: [], summary: { total: 0, timeframe, groupBy } }; } // Group leads by date/time period const groupedLeads = {}; leads.forEach(lead => { // Use dateCreated field from Dealer.K API format: "2025-06-23 18:19:17" const leadDate = new Date(lead.dateCreated || lead.created_at || lead.date || lead.createdAt); // Skip invalid dates if (isNaN(leadDate.getTime())) { logger.warn('Invalid lead date encountered', { leadId: lead.id, dateCreated: lead.dateCreated }); return; } let groupKey; if (groupBy === 'day') { groupKey = leadDate.toISOString().split('T')[0]; } else if (groupBy === 'week') { const weekStart = new Date(leadDate); weekStart.setDate(leadDate.getDate() - leadDate.getDay()); groupKey = weekStart.toISOString().split('T')[0]; } if (!groupedLeads[groupKey]) { groupedLeads[groupKey] = []; } groupedLeads[groupKey].push(lead); }); // Convert to trends array const trends = Object.entries(groupedLeads) .map(([date, dateLeads]) => ({ date, count: dateLeads.length, leads: dateLeads })) .sort((a, b) => new Date(a.date) - new Date(b.date)); const result = { trends, summary: { total: leads.length, timeframe, groupBy, avgPerPeriod: leads.length / trends.length || 0, dateRange: { from: dateFromStr, to: dateToStr } } }; logger.info(`Lead trends analysis completed`, { totalLeads: leads.length, trendsCount: trends.length, avgPerPeriod: result.summary.avgPerPeriod, timeframe, groupBy }); return result; } catch (error) { logger.error('Failed to get leads trends', error); throw error; } } module.exports = { getLeads, getLeadsForVehicle, getLeadsTrends, makeLeadsApiRequest };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/loukach/stockspark-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server