diff --git a/manifest.master.yml b/manifest.master.yml index 1b7c5bf..f349a3e 100644 --- a/manifest.master.yml +++ b/manifest.master.yml @@ -382,6 +382,12 @@ callbacks: type: objecttype objecttypes: - write_event + - name: "write (insert/update) an object over the api" + callback: write_db + filter: + type: objecttype + objecttypes: + - write_db - name: "bounce json back for 'bounce'" callback: bounce filter: @@ -453,7 +459,21 @@ callbacks: type: body args: - type: value - value: "%_exec.pluginDir%/server/db_pre_save+write_event/write_event.py" + value: "%_exec.pluginDir%/server/db_pre_save+write_api/write_event.py" + write_db: + exec: + service: python3 + commands: + - prog: python3 + stdin: + type: body + stderr: + type: body + stdout: + type: body + args: + - type: value + value: "%_exec.pluginDir%/server/db_pre_save+write_api/write_db.py" check: exec: service: "node" diff --git a/server/db_pre_save+write_api/util.py b/server/db_pre_save+write_api/util.py new file mode 100644 index 0000000..a2649e6 --- /dev/null +++ b/server/db_pre_save+write_api/util.py @@ -0,0 +1,130 @@ +# encoding: utf-8 + + +import json +import sys +import requests + + +# plugin response functions + +# the direct response from the plugin uses stdin and stderr + + +def stdout(msg: str): + sys.stdout.write(msg) + sys.stdout.write('\n') + + +def stderr(msg: str): + sys.stderr.write(msg) + sys.stderr.write('\n') + + +def return_response(response: dict): + stdout(json.dumps(response)) + exit(0) + + +def return_error_response(error: str): + stderr(f'error in fylr-plugin-example: {error}') + return_response( + { + 'error': { + 'code': 'fylr-plugin-example.error', + 'statuscode': 400, + 'description': error, + 'error': error, + }, + }, + ) + + +# ----------------------------- + +# helper functions to parse callback data from fylr + +# the api url and access token are necessary if the plugin +# needs to read/write more data over the fylr api + + +def get_api_url(callback_data): + url = get_json_value(callback_data, 'info.api_url') + if not url: + return_error_response('info.api_url missing!') + return f'{url}/api/v1' + + +def get_access_token(callback_data): + access_token = get_json_value(callback_data, 'info.api_user_access_token') + if not access_token: + return_error_response('info.api_user_access_token missing!') + return access_token + + +# ----------------------------- + +# fylr api functions + +# the plugin can call the fylr api to read/write more data + + +def fylr_api_headers(access_token): + return {'authorization': f'Bearer {access_token}'} + + +def get_from_api(api_url, path, access_token): + resp = requests.get( + url=f'{api_url}/{path}', + headers=fylr_api_headers(access_token), + ) + + return resp.text, resp.status_code + + +def post_to_api(api_url, path, access_token, payload=None): + resp = requests.post( + url=f'{api_url}/{path}', + headers=fylr_api_headers(access_token), + data=payload, + ) + + return resp.text, resp.status_code + + +# ----------------------------- + + +def get_json_value(js, path, expected=False, split_char='.'): + + current = js + path_parts = [] + current_part = '' + + for i in range(len(path)): + if path[i] != split_char: + current_part += path[i] + if i == len(path) - 1: + path_parts.append(current_part) + continue + + if i > 0 and path[i - 1] == '\\': + current_part += path[i] + continue + + if len(current_part) > 0: + path_parts.append(current_part) + current_part = '' + + for path_part in path_parts: + path_part = path_part.replace('\\' + split_char, split_char) + + if not isinstance(current, dict) or path_part not in current: + if expected: + raise Exception('expected: ' + path_part) + else: + return None + + current = current[path_part] + + return current diff --git a/server/db_pre_save+write_api/write_db.py b/server/db_pre_save+write_api/write_db.py new file mode 100644 index 0000000..3edb96b --- /dev/null +++ b/server/db_pre_save+write_api/write_db.py @@ -0,0 +1,88 @@ +# encoding: utf-8 + + +import sys +import json +import util + + +def main(): + + # read the callback data from fylr + callback_data = json.loads(sys.stdin.read()) + + # get the server api url and access token + api_url = util.get_api_url(callback_data) + + # get the oauth2 access token for the api + access_token = util.get_access_token(callback_data) + + objects = util.get_json_value(callback_data, 'objects') + if not isinstance(objects, list): + util.return_response(callback_data) + + updated_objects = [] + + # iterate over the objects and count how many were inserted/updated + for obj in objects: + + if not isinstance(obj, dict): + continue + + # get the "titel" from the object, + # if it is set create a new linked object with the same name. + # insert the object over the api and link it in this object + objecttype = util.get_json_value(obj, '_objecttype') + title = util.get_json_value(obj, f'{objecttype}.titel') + if not title: + continue + + # create a new "linked_object" payload + # and post it to the api/v1/db endpoint + resp_text, statuscode = util.post_to_api( + api_url=api_url, + path='db/linked_object', + access_token=access_token, + payload=json.dumps( + [ + { + '_comment': '', + '_mask': "_all_fields", + '_objecttype': "linked_object", + "linked_object": { + "_version": 1, + "name": title, + }, + } + ], + indent=4, + ), + ) + if statuscode != 200: + # could not insert the new linked object -> api error + util.return_error_response( + f'could not insert linked_object: api error (code {statuscode}): {resp_text}' + ) + + # use a lookup to insert the new linked object + obj[objecttype]['link'] = { + '_mask': "_all_fields", + '_objecttype': "linked_object", + "linked_object": { + "_version": 1, + "lookup:_id": { + "name": title, + }, + }, + } + + # this object was updated, it must be returned to fylr + updated_objects.append(obj) + + # only return the objects which were updated. + # fylr will save all other objects from the callback without any changes + util.return_response({"objects": updated_objects}) + + +if __name__ == '__main__': + main() diff --git a/server/db_pre_save+write_event/write_event.py b/server/db_pre_save+write_api/write_event.py similarity index 52% rename from server/db_pre_save+write_event/write_event.py rename to server/db_pre_save+write_api/write_event.py index d7466c9..ff8720f 100644 --- a/server/db_pre_save+write_event/write_event.py +++ b/server/db_pre_save+write_api/write_event.py @@ -8,23 +8,18 @@ def main(): - orig_data = json.loads(sys.stdin.read()) + # read the callback data from fylr + callback_data = json.loads(sys.stdin.read()) - # get the server url - api_url = util.get_json_value(orig_data, 'info.api_url') - if api_url is None: - util.return_error_response('info.api_url missing!') - api_url += '/api/v1' + # get the server api url and access token + api_url = util.get_api_url(callback_data) - # get a session token - access_token = util.get_json_value( - orig_data, 'info.api_user_access_token') - if access_token is None: - util.return_error_response('info.api_user_access_token missing!') + # get the oauth2 access token for the api + access_token = util.get_access_token(callback_data) - objects = util.get_json_value(orig_data, 'objects') + objects = util.get_json_value(callback_data, 'objects') if not isinstance(objects, list): - util.return_response(orig_data) + util.return_response(callback_data) objecttype_count = {} @@ -42,7 +37,7 @@ def main(): 'updated': 0, } - version = util.get_json_value(obj, objecttype + '._version') + version = util.get_json_value(obj, f'{objecttype}._version') if version == 1: objecttype_count[objecttype]['inserted'] += 1 else: @@ -54,15 +49,19 @@ def main(): api_url=api_url, path='event?background=1', access_token=access_token, - payload=util.dumpjs({ - 'event': { - 'type': 'EXAMPLE_PLUGIN_OBJECT_STATISTICS', - 'objecttype': objecttype, - 'info': objecttype_count[objecttype] - } - }) + payload=json.dumps( + { + 'event': { + 'type': 'EXAMPLE_PLUGIN_OBJECT_STATISTICS', + 'objecttype': objecttype, + 'info': objecttype_count[objecttype], + } + }, + indent=4, + ), ) + # return an empty objects array, this indicates to fylr that there were no changes in the objects util.return_response({"objects": []}) diff --git a/server/db_pre_save+write_event/util.py b/server/db_pre_save+write_event/util.py deleted file mode 100644 index ff7c5ba..0000000 --- a/server/db_pre_save+write_event/util.py +++ /dev/null @@ -1,95 +0,0 @@ -# encoding: utf-8 - - -import json -import sys -import requests - - -def get_json_value(js, path, expected=False, split_char='.'): - - current = js - path_parts = [] - current_part = '' - - for i in range(len(path)): - if path[i] != split_char: - current_part += path[i] - if i == len(path) - 1: - path_parts.append(current_part) - continue - - if i > 0 and path[i - 1] == '\\': - current_part += path[i] - continue - - if len(current_part) > 0: - path_parts.append(current_part) - current_part = '' - - for path_part in path_parts: - path_part = path_part.replace('\\' + split_char, split_char) - - if not isinstance(current, dict) or path_part not in current: - if expected: - raise Exception('expected: ' + path_part) - else: - return None - - current = current[path_part] - - return current - - -def dumpjs(js, indent=4): - return json.dumps(js, indent=indent) - - -# plugin response functions - - -def stdout(line): - sys.stdout.write(line) - sys.stdout.write('\n') - - -def stderr(line): - sys.stderr.write(line) - sys.stderr.write('\n') - - -def return_response(response): - stdout(dumpjs(response)) - exit(0) - - -def return_error_response(error): - stderr(error) - exit(1) - -# fylr api functions - - -def fylr_api_headers(access_token): - return { - 'authorization': 'Bearer ' + access_token, - } - - -def get_from_api(api_url, path, access_token): - - resp = requests.get( - api_url + '/' + path, - headers=fylr_api_headers(access_token)) - - return resp.text, resp.status_code - - -def post_to_api(api_url, path, access_token, payload=None): - - resp = requests.post( - api_url + '/' + path, - headers=fylr_api_headers(access_token), - data=payload) - - return resp.text, resp.status_code