Dec 08, 2023 02:24 PM
Hello Airtable Community.
I've reached my wits end and so has chatGPT 🙂 I am using the following scripting code in an Automation to extract some data from the record that triggered the automation and insert into an external API. Most of it is working as expected until the very end of the first function where I keep getting
CONSOLE.ERROR
"Error processing Airtable update:"
"Unexpected end of JSON input"
I've tried lots of logs to understand the input and everything appears in order to me. JSON is coming back fine in each of the "console.logs" indicated in the code here. An example here:
What could be causing this error? Source code below:
// Function to escape HTML characters
function escapeHtml(input) {
return input.replace(/</g, "<").replace(/>/g, ">");
}
// Airtable Script to trigger on "Terpene Tags" field update
// Airtable Base ID and Table Name
const AIRTABLE_BASE_ID = 'XXX';
const AIRTABLE_TABLE_NAME = 'XXX';
// Personal Access Token for Airtable
const AIRTABLE_PERSONAL_ACCESS_TOKEN = 'XXX';
// External API endpoint
const EXTERNAL_API_ENDPOINT = 'https://api.pos.dutchie.com/package/add-tags';
const EXTERNAL_API_KEY = 'XXX'; // Replace with your actual API key
// Relevant roomIds
const RELEVANT_ROOM_IDS = ['10650', '6454'];
// Airtable Script function to handle updates
async function onTerpeneTagsUpdate(event) {
try {
const recordId = event ? event.recordId : undefined;
console.log('Record Id:', recordId);
if (!recordId) {
console.error('RecordId is missing. Exiting script.');
return;
}
// Fetch data from Airtable
const airtableRecord = await fetchAirtableRecord(recordId);
console.log('Airtable Record:', airtableRecord);
// Check if 'fields' property exists before accessing it
if (airtableRecord && airtableRecord.fields) {
// Access 'fields' property safely
console.log(airtableRecord.fields);
// Now you can access specific properties of 'fields'
const productName = airtableRecord.fields["ProductIdNumber"];
const productTagsString = airtableRecord.fields["Terpene Names"];
console.log("productName:", productName);
console.log("productTags:", productTagsString);
// Convert the comma-separated string to an array
const productTags = productTagsString.split(',').map(tag => tag.trim());
console.log("Terpene Tags As Array:", productTags);
// Add more processing logic as needed
} else {
// Handle the case where 'fields' is undefined
console.error("Error: 'fields' property is undefined");
// You might want to handle this error appropriately, depending on your use case
return; // Exit the function if 'fields' is undefined
}
if (!airtableRecord || !airtableRecord.fields) {
console.error('Airtable record not found or has missing fields. Exiting script.');
return;
}
// Use the new numeric field for productId
const productId = airtableRecord.fields.ProductIdNumber;
// Fetch data from /inventory endpoint
const inventoryData = await fetchInventoryData();
console.log("Inventory Data:", inventoryData);
// Filter relevant packages (considering roomId for sale)
const relevantPackages = inventoryData.filter(
(package) =>
package.fields.productId === productId &&
RELEVANT_ROOM_IDS.includes(package.fields.roomId)
);
// Build payload
const payload = {
packageIds: relevantPackages.map((p) => p.fields.packageId),
tags: airtableRecord.fields['Terpene Names'], // Assuming you have the "Terpene Names" field
};
// Make API call to external endpoint
await makeExternalAPICall(payload);
console.log('Tags added successfully');
} catch (error) {
console.error('Error processing Airtable update:', error.message);
}
}
let parsedData; // Declare parsedData at a higher scope
// Function to safely parse JSON
function parseJSONSafe(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("Error parsing JSON:", error);
return null;
}
}
// Updated fetchAirtableRecord function
async function fetchAirtableRecord(recordId) {
const response = await fetch(
`https://api.airtable.com/v0/${AIRTABLE_BASE_ID}/${AIRTABLE_TABLE_NAME}/${recordId}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${AIRTABLE_PERSONAL_ACCESS_TOKEN}`,
},
}
);
console.log('Airtable API Response:', response.status, response.statusText);
console.log('Airtable API Headers:', response.headers);
if (!response.ok) {
console.error(`Failed to fetch Airtable record: ${response.status} - ${response.statusText}`);
return null;
}
const responseBody = await response.text();
console.log("Airtable Response Body:", responseBody);
// Safely parse the JSON
const parsedData = parseJSONSafe(responseBody);
if (!parsedData || !parsedData.fields) {
console.error("Error: Invalid Airtable record structure");
return null;
}
// Access the correct key for the record ID
const id = parsedData.id;
console.log("Record ID:", id);
// Now you can use the "id" as needed
// Return the parsed data
return parsedData;
}
// Function to fetch data from /inventory endpoint
async function fetchInventoryData() {
const response = await fetch('https://api.pos.dutchie.com/inventory');
const data = await response.json();
return data.records;
}
// Function to make API call to external endpoint
async function makeExternalAPICall(payload) {
const response = await fetch(EXTERNAL_API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json-patch+json',
'Authorization': `Bearer ${EXTERNAL_API_KEY}`, // Include your API key here
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Failed to make external API call: ${response.status} - ${response.statusText}`);
}
}
// Example: Trigger this function when Airtable record is updated
const event = input.config(); // Use input.config() to get the input data from the automation
onTerpeneTagsUpdate(event);
Any ideas?