import urllib.parse import requests from django.db import models from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from passerelle.base.models import BaseResource, HTTPResource from passerelle.utils.api import endpoint from passerelle.utils.conversion import exception_to_text from passerelle.utils.jsonresponse import APIError MULT_SCHEMA = { '$schema': 'http://json-schema.org/draft-04/schema#', 'title': 'Multi-criterion search', 'unflatten': True, 'description': '', 'type': 'object', 'required': ['search_name', 'criterions'], 'properties': { 'search_name': { 'description': _('Search name, as specified in the Esabora webservice'), 'type': 'string', 'examples': ['WS_ETAT_DOSSIER_SAS'], }, 'criterions': { 'description': _('A mapping of criterions'), 'type': 'object', 'examples': [{'SAS_Référence': 'HISTO0001'}], }, }, } DO_TREATMENT_SCHEMA = { '$schema': 'http://json-schema.org/draft-04/schema#', 'unflatten': True, 'title': 'Treatment creation', 'description': 'Additional fields in the payload will be transmitted to the Esabora service.', 'type': 'object', 'required': ['treatment_name'], 'properties': { 'endpoint': { 'description': _('Endpoint name, such as modbdd or addevt. Defaults to modbdd'), 'type': 'string', 'examples': ['modbdd'], }, 'treatment_name': { 'description': _('Treatment name, as specified in the Esabora service'), 'type': 'string', 'examples': ['IMPORT HISTOLOGE'], }, }, } class Esabora(BaseResource, HTTPResource): service_url = models.URLField( blank=False, verbose_name=_('Service URL'), help_text=_('Base Web Service URL, such as https://example.domain/ws/rest/'), ) api_key = models.CharField(max_length=256, default='', blank=True, verbose_name=_('API key')) category = _('Business Process Connectors') class Meta: verbose_name = _('Esabora') def post(self, path, payload, **kwargs): url = urllib.parse.urljoin(self.service_url, path) headers = {'Authorization': f'Bearer {self.api_key}'} try: response = self.requests.post(url, json=payload, headers=headers, timeout=5, **kwargs) except requests.RequestException as e: raise APIError( 'Esabora platform "%s" connection error: %s' % (self.service_url, exception_to_text(e)), log_error=True, data={ 'code': 'connection-error', 'service_url': self.service_url, 'error': str(e), }, ) try: data = response.json() except requests.JSONDecodeError as e: raise APIError( 'Esabora platform "%s" invalid JSON response: %s' % (self.service_url, exception_to_text(e)), log_error=True, data={ 'status_code': response.status_code, }, ) if not response.ok: raise APIError( 'Esabora platform "%s" answered with HTTP error' % (self.service_url), log_error=True, data={ 'status_code': response.status_code, 'content': data, }, ) return data @endpoint( name='do-search', description=_('Multi-criterion search'), perm='can_access', methods=['post'], post={'request_body': {'schema': {'application/json': MULT_SCHEMA}}}, json_schema_response={}, ) def do_search(self, request, post_data): payload = { 'searchName': post_data['search_name'], 'criterionList': [ {'criterionName': name, 'criterionValueList': [value]} for name, value in post_data['criterions'].items() ], } data = self.post('mult/', payload, params={'task': 'doSearch'}) columns = {slugify(c).replace('-', '_'): c for c in data['columnList']} keys = {slugify(c).replace('-', '_'): c for c in data['keyList']} cleaned_data = { 'meta': { 'nbResults': data['nbResults'], 'searchId': data['searchId'], 'columns_name': columns, 'keys_name': keys, }, 'data': [ esabora_row_to_object(list(columns.keys()), list(keys.keys()), row) for row in data['rowList'] ], } return cleaned_data @endpoint( name='do-treatment', description=_('Create a new treatment'), perm='can_access', methods=['post'], post={'request_body': {'schema': {'application/json': DO_TREATMENT_SCHEMA}}}, json_schema_response={}, ) def do_treatment(self, request, post_data): endpoint = post_data.pop('endpoint', None) or 'modbdd' payload = get_treatment_payload(post_data) data = self.post(f'{endpoint}/', payload, params={'task': 'doTreatment'}) keys = [slugify(c).replace('-', '_') for c in data['keyList']] cleaned_data = esabora_row_to_object([], keys, data) cleaned_data['action'] = data['action'] return cleaned_data def esabora_row_to_object(columns, key_list, row): key_data_list = row.get('keyDataList', []) all_values = row.get('columnDataList', []) + key_data_list obj = dict(zip(columns + key_list, all_values)) obj['text'] = all_values[0] if all_values else None if key_data_list: obj['id'] = key_data_list[0] return obj def get_treatment_payload(post_data): payload = {'treatmentName': post_data.pop('treatment_name'), 'fieldList': []} for key, value in post_data.items(): field = { 'fieldName': key, } if isinstance(value, list): populate_document_field(field, value) elif isinstance(value, dict): populate_document_field(field, [value]) else: field['fieldValue'] = value payload['fieldList'].append(field) return payload def populate_document_field(field, value): field['fieldDocumentUpdate'] = 1 field['fieldValue'] = [] # one or more files were sent, # let's compute their size in bytes based on the base64 payload (without = padding) # cf https://stackoverflow.com/a/45401395/2844093 for f in value: if not f: continue size = (len(f['content']) * 3) / 4 - f['content'].count('=', -2) field['fieldValue'].append( {'documentContent': f['content'], 'documentName': f['filename'], 'documentSize': size} )