- First 'working' version of the Zapier plugin. Needs further debugging and needs additional functionalty (only add_document.js)

This commit is contained in:
Josako
2024-12-12 16:36:41 +01:00
parent d35ec9f5ae
commit 46c60b36a0
14 changed files with 4875 additions and 17 deletions

View File

@@ -117,8 +117,7 @@ class EveAI_API {
throw new Exception("API error ({$error_type}): {$error_message}");
}
return $response_data
// return $body;
return $body;
} catch (Exception $e) {
error_log("EveAI API Exception: " . $e->getMessage());
throw $e;

View File

@@ -0,0 +1,4 @@
{
"id": 216725,
"key": "App216725"
}

View File

@@ -0,0 +1,24 @@
# eveai_integration
This Zapier integration project is generated by the `zapier init` CLI command.
These are what you normally do next:
```bash
# Install dependencies
npm install # or you can use yarn
# Run tests
zapier test
# Register the integration on Zapier if you haven't
zapier register "App Title"
# Or you can link to an existing integration on Zapier
zapier link
# Push it to Zapier
zapier push
```
Find out more on the latest docs: https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md.

View File

@@ -0,0 +1,93 @@
// api_client.js
const BASE_URL = 'https://evie.askeveai.com/api/api/v1';
class EveAIApiClient {
constructor(z, bundle) {
this.z = z;
this.bundle = bundle;
}
async ensure_valid_token() {
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const token = this.bundle.authData.access_token;
const tokenExpiry = this.bundle.authData.token_expiry;
// Check if token is expired or will expire in next 30 seconds
if (!token || !tokenExpiry || currentTime + 30 >= tokenExpiry) {
this.z.console.log('Token missing or expiring soon, requesting new token...');
const response = await this.z.request({
url: `${BASE_URL}/auth/token`,
method: 'POST',
body: {
tenant_id: this.bundle.authData.tenant_id,
api_key: this.bundle.authData.api_key,
},
});
if (response.status !== 200) {
throw new Error(`Failed to get access token: ${response.status}`);
}
const data = response.json;
// Update the bundle's authData
this.bundle.authData.access_token = data.access_token;
this.bundle.authData.token_expiry = currentTime + data.expires_in;
this.z.console.log('New token obtained:', {
token_prefix: data.access_token.substring(0, 10) + '...',
expires_in: data.expires_in,
expiry_time: this.bundle.authData.token_expiry
});
return data.access_token;
}
this.z.console.log('Using existing valid token');
return token;
}
async make_request(method, endpoint, data = null) {
try {
// Ensure we have a valid token
const token = await this.ensure_valid_token();
this.z.console.log('Making request:', {
method,
endpoint,
token_prefix: token.substring(0, 10) + '...'
});
const requestConfig = {
url: `${BASE_URL}${endpoint}`,
method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
};
if (data) {
requestConfig.body = data;
}
const response = await this.z.request(requestConfig);
this.z.console.log('Response received:', {
status: response.status,
data: response.json
});
return response.json;
} catch (error) {
this.z.console.error('Request failed:', {
error: error.message,
response: error.response
});
throw error;
}
}
}
module.exports = EveAIApiClient;

View File

@@ -0,0 +1,140 @@
const EveAIApiClient = require('./api_client');
const handleError = (z, error) => {
// Log the full error for debugging
z.console.error('Authentication error:', {
message: error.message,
response: error.response ? {
status: error.response.status,
data: error.response.data,
headers: error.response.headers
} : 'No response',
stack: error.stack
});
// If we have a response with a message from the API, use it
if (error.response) {
if (error.response.status) {
switch (error.response.status) {
case 400:
throw new Error('Invalid request. Please verify your Tenant ID and API Key format.');
case 401:
throw new Error('Authentication failed. Please verify your credentials and ensure your API Key is active.');
case 403:
throw new Error('Access forbidden. Your API Key may not have the required permissions.');
case 404:
throw new Error('Authentication service not found. Please verify the API URL.');
case 429:
throw new Error('Too many authentication attempts. Please try again later.');
case 500:
throw new Error('EveAI server encountered an error. Please try again later.');
default:
throw new Error(`Unexpected error (${error.response.status}). Please contact support if this persists.`);
}
}
// If we have error data but no status, try to use the error message
if (error.response.data) {
if (error.response.data.message) {
throw new Error(`API Error: ${error.response.data.message}`);
}
if (typeof error.response.data === 'string') {
throw new Error(`API Error: ${error.response.data}`);
}
}
}
// Handle network errors
if (error.message.includes('ECONNREFUSED')) {
throw new Error('Unable to connect to EveAI. Please check your network connection.');
}
if (error.message.includes('ETIMEDOUT')) {
throw new Error('Connection to EveAI timed out. Please try again.');
}
// Generic error for unhandled cases
throw new Error(`Authentication failed: ${error.message}`);
};
const testAuth = async (z, bundle) => {
try {
const client = new EveAIApiClient(z, bundle);
await client.ensure_valid_token();
// Make a test request to verify the token works
const response = await client.make_request('GET', '/auth/verify');
// Log successful authentication
z.console.log('Authentication successful', {
tenant_id: bundle.authData.tenant_id,
token_prefix: bundle.authData.access_token.substring(0, 10) + '...',
expiry: new Date(bundle.authData.token_expiry * 1000).toISOString()
});
return response;
} catch (error) {
// Use our enhanced error handler
handleError(z, error);
}
};
module.exports = {
type: 'session',
fields: [
{
helpText:
"The tenant_id provided to you by email, or to be created / retrieved in Evie's administrative interface (https://evie.askeveai.com/admin)",
computed: false,
key: 'tenant_id',
required: true,
label: 'Tenant ID',
type: 'string',
},
{
helpText:
"The api_key provided to you by email, or to be created / retrieved in Evie's administrative interface (https://evie.askeveai.com/admin)",
computed: false,
key: 'api_key',
required: true,
label: 'API Key',
type: 'password',
},
],
sessionConfig: {
perform: async (z, bundle) => {
try {
const client = new EveAIApiClient(z, bundle);
const token = await client.ensure_valid_token();
z.console.log('Session token obtained', {
token_prefix: token.substring(0, 10) + '...',
expiry: new Date(bundle.authData.token_expiry * 1000).toISOString()
});
return {
access_token: token,
token_expiry: bundle.authData.token_expiry
};
} catch (error) {
handleError(z, error);
}
}
},
test: testAuth,
};

View File

@@ -0,0 +1,262 @@
const EveAIApiClient = require('../api_client');
module.exports = {
display: {
description: "This action uploads a new document to Evie's Library",
hidden: false,
label: 'Add a new document to Evie',
},
key: 'add_document',
noun: 'Document',
operation: {
inputFields: [
{
key: 'catalog_id',
label: 'Catalog ID',
type: 'integer',
helpText:
"The ID of the Catalog in Evie's Library you want to add the document to.",
required: true,
list: false,
altersDynamicFields: false,
},
{
key: 'file',
label: 'The File Content',
type: 'file',
helpText:
"The content of the file that needs to uploaded and indexed in Evie's Library",
required: true,
list: false,
altersDynamicFields: false,
},
{
key: 'name',
label: 'Document Name',
type: 'string',
helpText: 'The name you want to give the Document.',
required: false,
list: false,
altersDynamicFields: false,
},
{
key: 'language',
label: 'Document Language',
type: 'string',
default: 'en',
helpText: 'Two-letter-code of the language the document is written in.',
required: true,
list: false,
altersDynamicFields: false,
},
{
key: 'user_context',
label: 'User Context',
type: 'text',
helpText:
'Contextual information you want to add to the Document. If you have structured information to be shared, you can better add this information to the User Metadata, which allows for json to be uploaded.',
required: false,
list: false,
altersDynamicFields: false,
},
{
key: 'valid_from',
label: 'Valid From',
type: 'datetime',
helpText:
'The moment this document is valid. When no value is given, the current data will be used.',
required: false,
list: false,
altersDynamicFields: false,
},
{
key: 'metadata_service',
label: 'Service',
type: 'string',
default: 'Zapier',
helpText: "By default we use 'Zapier' as service name. However, if you need to change that to e.g. give an indication of the Zapier flow, you can change this value.",
required: true,
},
{
key: 'metadata_source',
label: 'Source App',
type: 'string',
helpText: "The source app of the document's origin. e.g. 'Dropbox' if the document is provided through Dropbox, or 'Google Docs' if that happens to be the origin of the document.",
required: false,
},
{
key: 'metadata_unique_id',
label: 'Unique ID',
type: 'string',
helpText: 'An unique identifier, provided by the source system, if that is available.',
required: false,
},
{
key: 'metadata_unique_url',
label: 'Unique URL',
type: 'string',
helpText: "A unique URL that is provided by the source system, if that's available",
required: false,
},
{
key: 'additional_metadata',
label: 'Additional Metadata',
helpText: "Extra metadata you'd like to add to the document",
dict: true,
required: false,
altersDynamicFields: false,
},
{
key: 'catalog_properties',
label: 'Catalog Properties',
helpText:
'Depending on the Catalog ID provided, you can add the required key-value pairs here.',
dict: true,
required: false,
altersDynamicFields: false,
},
],
perform: async (z, bundle) => {
try {
z.console.log("Starting New Log Trace for add_document");
z.console.log("=======================================");
const client = new EveAIApiClient(z, bundle);
// Prepare base metadata
const baseMetadata = {
service: bundle.inputData.metadata_service || 'Zapier',
source: bundle.inputData.metadata_source || '',
unique_id: bundle.inputData.metadata_unique_id || '',
unique_url: bundle.inputData.metadata_unique_url || '',
};
// If there's additional metadata, merge it
if (bundle.inputData.additional_metadata) {
Object.assign(baseMetadata, bundle.inputData.additional_metadata);
}
// Get the file content
const filePromise = z.stashFile(bundle.inputData.file);
const file = await filePromise;
// const temp_url = z.stashFile(bundle.inputData.file);
// Create request data as an object
const requestData = {
catalog_id: bundle.inputData.catalog_id,
language: bundle.inputData.language,
temp_url: file, // This will be handled by z.request automatically
user_metadata: JSON.stringify(baseMetadata),
};
// Add name property if it exists
if (bundle.inputData.name) {
requestData.name = bundle.inputData.name;
}
// Add user_context property if it exists
if (bundle.inputData.user_context) {
requestData.user_context = bundle.inputData.user_context;
}
// Add valid_from property if it exists
if (bundle.inputData.valid_from) {
requestData.valid_from = bundle.inputData.valid_from;
}
// Add catalog properties if they exist
if (bundle.inputData.catalog_properties) {
requestData.catalog_properties = JSON.stringify(bundle.inputData.catalog_properties);
}
// Make request to API
return await client.make_request('POST', '/documents/add_document_through_url', requestData);
} catch (error) {
// Enhanced error logging
z.console.error('Error details:', {
message: error.message,
response: error.response ? {
status: error.response.status,
headers: error.response.headers,
data: error.response.data
} : 'No response',
request: error.request ? {
method: error.request.method,
url: error.request.url,
headers: error.request.headers
} : 'No request'
});
throw error;
}
}
// perform: async (z, bundle) => {
// try {
// z.console.log("Starting New Log Trace for add_document")
// z.console.log("=======================================")
//
// // Prepare base metadata
// const baseMetadata = {
// service: bundle.inputData.metadata_service || 'Zapier',
// source: bundle.inputData.metadata_source,
// unique_id: bundle.inputData.metadata_unique_id,
// unique_url: bundle.inputData.metadata_unique_url,
// };
//
// // If there's additional metadata, merge it with the base metadata
// if (bundle.inputData.additional_metadata) {
// Object.assign(baseMetadata, bundle.inputData.additional_metadata);
// }
//
// const requestData = {
//
// catalog_id: bundle.inputData.catalog_id,
// language: bundle.inputData.language,
//
// // Add optional fields if they exist
// name: bundle.inputData.name || undefined,
// user_context: bundle.inputData.user_context || undefined,
// valid_from: bundle.inputData.valid_from || undefined,
// user_metadata: JSON.stringify(baseMetadata),
// catalog_properties: JSON.stringify(bundle.inputData.catalog_properties) || undefined,
// file: z.stashFile(bundle.inputData.file),
// }
//
// // Make request to your API
// const response = await z.request({
// url: 'https://evie.askeveai.com/api/api/v1/documents/add_document',
// method: 'POST',
// body: requestData,
// headers: {
// 'Authorization': `Bearer ${bundle.authData.access_token}`,
// 'Content-Type': 'multipart/form-data',
// },
// });
//
// // Log the response for debugging
// z.console.log('API Response:', {
// status: response.status,
// body: response.data
// });
// // Return the parsed response
// return response.json;
// } catch (error) {
// // Enhanced error logging
// z.console.error('Error details:', {
// message: error.message,
// response: error.response ? {
// status: error.response.status,
// headers: error.response.headers,
// data: error.response.data
// } : 'No response',
// request: error.request ? {
// method: error.request.method,
// url: error.request.url,
// headers: error.request.headers
// } : 'No request'
// });
// throw error;
// }
// }
}
};

View File

@@ -0,0 +1,25 @@
const authentication = require('./authentication');
const addDocument = require('./creates/add_document');
module.exports = {
// This is just shorthand to reference the installed dependencies you have.
// Zapier will need to know these before we can upload.
version: require('./package.json').version,
platformVersion: require('zapier-platform-core').version,
// Register the authentication
authentication: authentication,
// If you want your trigger to show up, you better include it here!
triggers: {},
// If you want your searches to show up, you better include it here!
searches: {},
// If you want your creates to show up, you better include it here!
creates: {
[addDocument.key]: addDocument
},
resources: {},
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
{
"name": "eveai_integration",
"version": "1.0.3",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --testTimeout 10000"
},
"dependencies": {
"zapier-platform-core": "15.19.0"
},
"devDependencies": {
"jest": "^29.6.0"
},
"private": true
}