- First 'working' version of the Zapier plugin. Needs further debugging and needs additional functionalty (only add_document.js)
This commit is contained in:
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user