avocavo
Version:
Avocavo CLI - Structured nutrition data API with USDA-based calculations where applicable. Consistent nutrition estimates for apps and workflows. For informational use only. Not affiliated with or endorsed by USDA.
208 lines (180 loc) • 5.61 kB
JavaScript
const axios = require('axios');
const chalk = require('chalk');
class NutritionAPI {
constructor(apiKey, baseUrl = 'https://app.avocavo.app', timeout = 30000, auth = null) {
this.apiKey = apiKey;
this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
this.timeout = timeout;
this.auth = auth;
// Create axios instance with default configuration
this.client = axios.create({
timeout: this.timeout,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'avocavo-cli/1.1.0'
}
});
// Set authentication headers
if (this.apiKey) {
if (this.apiKey.startsWith('eyJ') || this.apiKey.includes('.')) {
// JWT token
this.client.defaults.headers['Authorization'] = `Bearer ${this.apiKey}`;
} else {
// API key
this.client.defaults.headers['X-API-Key'] = this.apiKey;
}
}
}
async analyzeIngredient(ingredient, verify = false, verbose = false) {
try {
const response = await this.client.post(`${this.baseUrl}/api/v2/nutrition/ingredient`, {
ingredient: ingredient
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async analyzeText(text, verbose = false) {
try {
const response = await this.client.post(`${this.baseUrl}/api/v2/nutrition/analyze`, {
text: text
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async analyzeRecipe(ingredients, servings = 1, verbose = false) {
try {
const response = await this.client.post(`${this.baseUrl}/api/v2/nutrition/recipe`, {
ingredients: ingredients,
servings: servings
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async analyzeBatch(ingredients, verbose = false) {
try {
// Transform array of strings to array of objects expected by batch endpoint
const ingredientObjects = ingredients.map((ingredient, index) => ({
ingredient: ingredient,
id: `item_${index + 1}`
}));
const response = await this.client.post(`${this.baseUrl}/api/v2/nutrition/batch`, {
ingredients: ingredientObjects
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async getAccountUsage() {
try {
const response = await this.client.get(`${this.baseUrl}/api/v2/nutrition/account/usage`);
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async healthCheck() {
try {
// Remove auth headers for health check
const headers = { ...this.client.defaults.headers };
delete headers['X-API-Key'];
delete headers['Authorization'];
const response = await this.client.get(`${this.baseUrl}/api/v2/nutrition/health`, {
headers: headers
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
// NEW UPC FUNCTIONALITY
async searchUPC(upc) {
try {
const response = await this.client.post(`${this.baseUrl}/api/v2/upc/ingredient`, {
upc: upc
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async searchUPCBatch(upcs) {
try {
const response = await this.client.post(`${this.baseUrl}/api/v2/upc/batch`, {
upcs: upcs
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
async upcHealthCheck() {
try {
// Remove auth headers for health check
const headers = { ...this.client.defaults.headers };
delete headers['X-API-Key'];
delete headers['Authorization'];
const response = await this.client.get(`${this.baseUrl}/api/upc/health`, {
headers: headers
});
return response.data;
} catch (error) {
throw this.handleError(error);
}
}
handleError(error) {
if (error.response) {
const status = error.response.status;
const data = error.response.data;
// Extract error message
let message = data?.error || data?.message || `HTTP ${status} Error`;
// Handle specific status codes
switch (status) {
case 401:
message = 'Invalid API key or authentication failed';
break;
case 402:
message = 'Payment required - upgrade your plan or add credits';
break;
case 403:
message = data?.error || 'Access denied - feature not available on your plan';
break;
case 429:
const limit = data?.limit;
const usage = data?.usage;
message = `Rate limit exceeded${limit ? ` (${usage}/${limit})` : ''}`;
break;
case 500:
case 502:
case 503:
message = 'Server error - please try again later';
break;
}
const apiError = new Error(message);
apiError.status = status;
apiError.response = data;
return apiError;
} else if (error.request) {
return new Error('Network error - please check your connection');
} else {
return new Error(error.message || 'Unknown error occurred');
}
}
}
// Helper function to format response time
function formatResponseTime(ms) {
if (ms < 1000) {
return chalk.green(`${ms}ms`);
} else if (ms < 3000) {
return chalk.yellow(`${ms}ms`);
} else {
return chalk.red(`${ms}ms`);
}
}
module.exports = { NutritionAPI, formatResponseTime };