- First 'working' version of the Zapier plugin. Needs further debugging and needs additional functionalty (only add_document.js)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from flask import request, session
|
||||
import time
|
||||
from flask_security import current_user
|
||||
import json
|
||||
|
||||
|
||||
def log_request_middleware(app):
|
||||
@@ -59,3 +60,97 @@ def log_request_middleware(app):
|
||||
@app.after_request
|
||||
def log_session_state_after(response):
|
||||
return response
|
||||
|
||||
|
||||
def register_request_debugger(app):
|
||||
@app.before_request
|
||||
def debug_request_info():
|
||||
"""Log consolidated request information for debugging"""
|
||||
# Skip health check endpoints
|
||||
if request.path.startswith('/_healthz') or request.path.startswith('/healthz'):
|
||||
return
|
||||
|
||||
# Gather all request information in a structured way
|
||||
debug_info = {
|
||||
"basic_info": {
|
||||
"method": request.method,
|
||||
"path": request.path,
|
||||
"content_type": request.content_type,
|
||||
"content_length": request.content_length
|
||||
},
|
||||
"environment": {
|
||||
"remote_addr": request.remote_addr,
|
||||
"user_agent": str(request.user_agent)
|
||||
}
|
||||
}
|
||||
|
||||
# Add headers (excluding sensitive ones)
|
||||
safe_headers = {k: v for k, v in request.headers.items()
|
||||
if k.lower() not in ('authorization', 'cookie', 'x-api-key')}
|
||||
debug_info["headers"] = safe_headers
|
||||
|
||||
# Add authentication info (presence only)
|
||||
auth_header = request.headers.get('Authorization', '')
|
||||
debug_info["auth_info"] = {
|
||||
"has_auth_header": bool(auth_header),
|
||||
"auth_type": auth_header.split(' ')[0] if auth_header else None,
|
||||
"token_length": len(auth_header.split(' ')[1]) if auth_header and len(auth_header.split(' ')) > 1 else 0,
|
||||
"header_format": 'Valid format' if auth_header.startswith('Bearer ') else 'Invalid format',
|
||||
"raw_header": auth_header[:10] + '...' if auth_header else None # Show first 10 chars only
|
||||
}
|
||||
|
||||
# Add request data based on type
|
||||
if request.is_json:
|
||||
try:
|
||||
json_data = request.get_json()
|
||||
if isinstance(json_data, dict):
|
||||
# Remove sensitive fields from logging
|
||||
safe_json = {k: v for k, v in json_data.items()
|
||||
if not any(sensitive in k.lower()
|
||||
for sensitive in ['password', 'token', 'secret', 'key'])}
|
||||
debug_info["request_data"] = {
|
||||
"type": "json",
|
||||
"content": safe_json
|
||||
}
|
||||
except Exception as e:
|
||||
debug_info["request_data"] = {
|
||||
"type": "json",
|
||||
"error": str(e)
|
||||
}
|
||||
elif request.form:
|
||||
safe_form = {k: v for k, v in request.form.items()
|
||||
if not any(sensitive in k.lower()
|
||||
for sensitive in ['password', 'token', 'secret', 'key'])}
|
||||
debug_info["request_data"] = {
|
||||
"type": "form",
|
||||
"content": safe_form
|
||||
}
|
||||
|
||||
# Add file information if present
|
||||
if request.files:
|
||||
debug_info["files"] = {
|
||||
name: {
|
||||
"filename": f.filename,
|
||||
"content_type": f.content_type,
|
||||
"content_length": f.content_length if hasattr(f, 'content_length') else None
|
||||
}
|
||||
for name, f in request.files.items()
|
||||
}
|
||||
|
||||
# Add CORS information if present
|
||||
cors_headers = {
|
||||
"origin": request.headers.get('Origin'),
|
||||
"request_method": request.headers.get('Access-Control-Request-Method'),
|
||||
"request_headers": request.headers.get('Access-Control-Request-Headers')
|
||||
}
|
||||
if any(cors_headers.values()):
|
||||
debug_info["cors"] = {k: v for k, v in cors_headers.items() if v is not None}
|
||||
|
||||
# Format the debug info as a pretty-printed JSON string with indentation
|
||||
formatted_debug_info = json.dumps(debug_info, indent=2, sort_keys=True)
|
||||
|
||||
# Log everything in a single statement
|
||||
app.logger.debug(
|
||||
"Request Debug Information\n",
|
||||
extra={"request_debug\n": formatted_debug_info}
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ from .api.auth import auth_ns
|
||||
from config.config import get_config
|
||||
from common.utils.celery_utils import make_celery, init_celery
|
||||
from common.utils.eveai_exceptions import EveAIException
|
||||
from common.utils.debug_utils import register_request_debugger
|
||||
|
||||
|
||||
def create_app(config_file=None):
|
||||
@@ -55,6 +56,9 @@ def create_app(config_file=None):
|
||||
# Register Error Handlers
|
||||
register_error_handlers(app)
|
||||
|
||||
# Register Request Debugger
|
||||
register_request_debugger(app)
|
||||
|
||||
@app.before_request
|
||||
def check_cors():
|
||||
if request.method == 'OPTIONS':
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from datetime import timedelta, datetime as dt, timezone as tz
|
||||
|
||||
from flask_restx import Namespace, Resource, fields
|
||||
from flask_jwt_extended import create_access_token, verify_jwt_in_request, get_jwt
|
||||
from flask_jwt_extended import create_access_token, verify_jwt_in_request, get_jwt, get_jwt_identity, jwt_required
|
||||
from common.models.user import Tenant, TenantProject
|
||||
from common.extensions import simple_encryption
|
||||
from flask import current_app, request
|
||||
from flask import current_app, jsonify, request
|
||||
from functools import wraps
|
||||
|
||||
auth_ns = Namespace('auth', description='Authentication related operations')
|
||||
|
||||
@@ -36,7 +37,6 @@ class Token(Resource):
|
||||
"""
|
||||
Get JWT token
|
||||
"""
|
||||
current_app.logger.debug(f'Token Requested {auth_ns.payload}')
|
||||
try:
|
||||
tenant_id = int(auth_ns.payload['tenant_id'])
|
||||
api_key = auth_ns.payload['api_key']
|
||||
@@ -71,16 +71,19 @@ class Token(Resource):
|
||||
current_app.logger.error(f"Project for given API key not found for Tenant: {tenant_id}")
|
||||
return {'message': "Invalid API key"}, 401
|
||||
|
||||
if "DOCAPI" not in matching_project.services:
|
||||
current_app.logger.error(f"Service DOCAPI not authorized for Project {matching_project.name} "
|
||||
f"for Tenant: {tenant_id}")
|
||||
return {'message': f"Service DOCAPI not authorized for Project {matching_project.name}"}, 403
|
||||
|
||||
# Get the JWT_ACCESS_TOKEN_EXPIRES setting from the app config
|
||||
expires_delta = current_app.config.get('JWT_ACCESS_TOKEN_EXPIRES', timedelta(minutes=15))
|
||||
|
||||
try:
|
||||
access_token = create_access_token(identity=tenant_id, expires_delta=expires_delta)
|
||||
additional_claims = {
|
||||
'services': matching_project.services,
|
||||
}
|
||||
access_token = create_access_token(
|
||||
identity=tenant_id,
|
||||
expires_delta=expires_delta,
|
||||
additional_claims=additional_claims
|
||||
)
|
||||
current_app.logger.debug(f"Created token: {access_token}")
|
||||
return {
|
||||
'access_token': access_token,
|
||||
'expires_in': expires_delta.total_seconds()
|
||||
@@ -145,4 +148,51 @@ class TokenRefresh(Resource):
|
||||
}, 200
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Token refresh failed: {str(e)}")
|
||||
return {'message': 'Token refresh failed'}, 401
|
||||
return {'message': 'Token refresh failed'}, 401
|
||||
|
||||
|
||||
@auth_ns.route('/services')
|
||||
class Services(Resource):
|
||||
@jwt_required()
|
||||
@auth_ns.doc(security='Bearer')
|
||||
@auth_ns.response(200, 'Success', {
|
||||
'services': fields.List(fields.String, description='List of allowed services for this token'),
|
||||
'tenant_id': fields.Integer(description='Tenant ID associated with this token')
|
||||
})
|
||||
@auth_ns.response(401, 'Invalid or expired token')
|
||||
def get(self):
|
||||
"""
|
||||
Get allowed services for the current token
|
||||
"""
|
||||
# Log the incoming authorization header
|
||||
auth_header = request.headers.get('Authorization')
|
||||
current_app.logger.debug(f"Received Authorization header: {auth_header}")
|
||||
|
||||
claims = get_jwt()
|
||||
tenant_id = get_jwt_identity()
|
||||
|
||||
return {
|
||||
'services': claims.get('services', []),
|
||||
'tenant_id': tenant_id
|
||||
}, 200
|
||||
|
||||
|
||||
# Decorate function to check for a particular service
|
||||
def requires_service(service_name):
|
||||
def decorator(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
# Get the JWT claims
|
||||
claims = get_jwt()
|
||||
services = claims.get('services', [])
|
||||
|
||||
if service_name not in services:
|
||||
return {
|
||||
'message': f'This endpoint requires the {service_name} service',
|
||||
'error': 'Insufficient permissions'
|
||||
}, 403
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import io
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
import requests
|
||||
from flask import current_app, request
|
||||
from flask_restx import Namespace, Resource, fields, reqparse
|
||||
from flask_jwt_extended import jwt_required, get_jwt_identity
|
||||
@@ -9,9 +11,9 @@ from werkzeug.datastructures import FileStorage
|
||||
from werkzeug.utils import secure_filename
|
||||
from common.utils.document_utils import (
|
||||
create_document_stack, process_url, start_embedding_task,
|
||||
validate_file_type, EveAIInvalidLanguageException, EveAIDoubleURLException, EveAIUnsupportedFileType,
|
||||
EveAIInvalidLanguageException, EveAIDoubleURLException, EveAIUnsupportedFileType,
|
||||
get_documents_list, edit_document, refresh_document, edit_document_version,
|
||||
refresh_document_with_info, lookup_document
|
||||
refresh_document_with_info, lookup_document, refresh_document_with_content
|
||||
)
|
||||
from common.utils.eveai_exceptions import EveAIException
|
||||
from eveai_api.api.auth import requires_service
|
||||
@@ -74,12 +76,17 @@ class AddDocument(Resource):
|
||||
|
||||
try:
|
||||
args = upload_parser.parse_args()
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error parsing arguments: {str(e)}")
|
||||
current_app.logger.error(f"Exception type: {type(e)}")
|
||||
raise
|
||||
|
||||
try:
|
||||
file = args['file']
|
||||
filename = secure_filename(file.filename)
|
||||
extension = filename.rsplit('.', 1)[1].lower()
|
||||
|
||||
validate_file_type(extension)
|
||||
# validate_file_type(extension)
|
||||
|
||||
api_input = {
|
||||
'catalog_id': args.get('catalog_id'),
|
||||
@@ -109,6 +116,105 @@ class AddDocument(Resource):
|
||||
document_ns.abort(500, 'Error adding document')
|
||||
|
||||
|
||||
# Models for AddDocumentThroughURL
|
||||
add_document_through_url = document_ns.model('AddDocumentThroughURL', {
|
||||
'catalog_id': fields.Integer(required=True, description='ID of the catalog the URL needs to be added to'),
|
||||
'temp_url': fields.String(required=True, description='Temporary URL of the document to add'),
|
||||
'name': fields.String(required=False, description='Name of the document'),
|
||||
'language': fields.String(required=True, description='Language of the document'),
|
||||
'user_context': fields.String(required=False, description='User context for the document'),
|
||||
'valid_from': fields.String(required=False, description='Valid from date for the document'),
|
||||
'user_metadata': fields.String(required=False, description='User metadata for the document'),
|
||||
'system_metadata': fields.String(required=False, description='System metadata for the document'),
|
||||
'catalog_properties': fields.String(required=False, description='The catalog configuration to be passed along (JSON '
|
||||
'format). Validity is against catalog requirements '
|
||||
'is not checked, and is the responsibility of the '
|
||||
'calling client.'),
|
||||
})
|
||||
|
||||
add_document_through_url_response = document_ns.model('AddDocumentThroughURLResponse', {
|
||||
'message': fields.String(description='Status message'),
|
||||
'document_id': fields.Integer(description='ID of the created document'),
|
||||
'document_version_id': fields.Integer(description='ID of the created document version'),
|
||||
'task_id': fields.String(description='ID of the embedding task')
|
||||
})
|
||||
|
||||
@document_ns.route('/add_document_through_url')
|
||||
class AddDocumentThroughURL(Resource):
|
||||
@jwt_required()
|
||||
@requires_service('DOCAPI')
|
||||
@document_ns.expect(add_document_through_url)
|
||||
@document_ns.response(201, 'Document added successfully', add_document_through_url)
|
||||
@document_ns.response(400, 'Validation Error')
|
||||
@document_ns.response(422, 'File could not be processed')
|
||||
@document_ns.response(500, 'Internal Server Error')
|
||||
def post(self):
|
||||
"""
|
||||
Add a new document using a URL. The URL can be temporary, and will not be stored.
|
||||
Mainly used for passing temporary URLs like used in e.g. Zapier
|
||||
"""
|
||||
tenant_id = get_jwt_identity()
|
||||
current_app.logger.info(f'Adding document through url for tenant {tenant_id}')
|
||||
|
||||
try:
|
||||
args = document_ns.payload
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error parsing arguments: {str(e)}")
|
||||
current_app.logger.error(f"Exception type: {type(e)}")
|
||||
raise
|
||||
|
||||
file_url = args['temp_url']
|
||||
current_app.logger.info(f"Downloading file from URL: {file_url}")
|
||||
try:
|
||||
response = requests.get(file_url, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
# Get filename from URL or use provided name
|
||||
filename = secure_filename(args.get('name') or file_url.split('/')[-1])
|
||||
extension = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
|
||||
|
||||
# Create FileStorage object from downloaded content
|
||||
file_content = io.BytesIO(response.content)
|
||||
file = FileStorage(
|
||||
stream=file_content,
|
||||
filename=filename,
|
||||
content_type=response.headers.get('content-type', 'application/octet-stream')
|
||||
)
|
||||
|
||||
current_app.logger.info(f"Successfully downloaded file: {filename}")
|
||||
except requests.RequestException as e:
|
||||
current_app.logger.error(f"Error downloading file: {str(e)}")
|
||||
return {'message': f'Error downloading file: {str(e)}'}, 422
|
||||
|
||||
try:
|
||||
# Prepare API input
|
||||
api_input = {
|
||||
'catalog_id': args.get('catalog_id'),
|
||||
'name': args.get('name') or filename,
|
||||
'language': args.get('language'),
|
||||
'user_context': args.get('user_context'),
|
||||
'valid_from': args.get('valid_from'),
|
||||
'user_metadata': args.get('user_metadata'),
|
||||
'catalog_properties': args.get('catalog_properties'),
|
||||
}
|
||||
|
||||
new_doc, new_doc_vers = create_document_stack(api_input, file, filename, extension, tenant_id)
|
||||
task_id = start_embedding_task(tenant_id, new_doc_vers.id)
|
||||
|
||||
return {
|
||||
'message': f'Processing on document {new_doc.name}, version {new_doc_vers.id} started. Task ID: {task_id}.',
|
||||
'document_id': new_doc.id,
|
||||
'document_version_id': new_doc_vers.id,
|
||||
'task_id': task_id
|
||||
}, 201
|
||||
except (EveAIInvalidLanguageException, EveAIUnsupportedFileType) as e:
|
||||
current_app.logger.error(f'Error adding document: {str(e)}')
|
||||
return {'message': str(e)}, 400
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'Error adding document: {str(e)}')
|
||||
return {'message': 'Error adding document'}, 500
|
||||
|
||||
|
||||
# Models for AddURL
|
||||
add_url_model = document_ns.model('AddURL', {
|
||||
'catalog_id': fields.Integer(required='True', description='ID of the catalog the URL needs to be added to'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Import all processor implementations to ensure registration
|
||||
from . import audio_processor, html_processor, pdf_processor
|
||||
from . import audio_processor, html_processor, pdf_processor, markdown_processor, docx_processor
|
||||
|
||||
# List of all available processor implementations
|
||||
__all__ = ['audio_processor', 'html_processor', 'pdf_processor']
|
||||
__all__ = ['audio_processor', 'html_processor', 'pdf_processor', 'markdown_processor', 'docx_processor']
|
||||
@@ -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;
|
||||
|
||||
4
integrations/Zapier/eveai_integration/.zapierapprc
Normal file
4
integrations/Zapier/eveai_integration/.zapierapprc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"id": 216725,
|
||||
"key": "App216725"
|
||||
}
|
||||
24
integrations/Zapier/eveai_integration/README.md
Normal file
24
integrations/Zapier/eveai_integration/README.md
Normal 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.
|
||||
93
integrations/Zapier/eveai_integration/api_client.js
Normal file
93
integrations/Zapier/eveai_integration/api_client.js
Normal 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;
|
||||
140
integrations/Zapier/eveai_integration/authentication.js
Normal file
140
integrations/Zapier/eveai_integration/authentication.js
Normal 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,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
262
integrations/Zapier/eveai_integration/creates/add_document.js
Normal file
262
integrations/Zapier/eveai_integration/creates/add_document.js
Normal 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;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
};
|
||||
25
integrations/Zapier/eveai_integration/index.js
Normal file
25
integrations/Zapier/eveai_integration/index.js
Normal 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: {},
|
||||
};
|
||||
4040
integrations/Zapier/eveai_integration/package-lock.json
generated
Normal file
4040
integrations/Zapier/eveai_integration/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
integrations/Zapier/eveai_integration/package.json
Normal file
16
integrations/Zapier/eveai_integration/package.json
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user