import json import logging import traceback import pydapper from ..schemas import model from .. import local_db from ..services.auth_guard import check_jwt from BreCal.database.update_database import evaluate_shipcall_state from BreCal.database.sql_utils import get_notification_for_shipcall_and_type, get_ship_data_for_id from BreCal.database.sql_queries import create_sql_query_shipcall_get from marshmallow import Schema, fields, ValidationError from BreCal.validators.validation_error import create_validation_error_response def GetShipcalls(options): """ No parameters, gets all entries """ try: pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) # query = SQLQuery.get_shipcalls(options) query = create_sql_query_shipcall_get(options) data = commands.query(query, model=model.Shipcall.from_query_row, buffered=True) for shipcall in data: # participant_query = SQLQuery.get_participants() # participants = commands.query(participant_query, model=dict, param={"shipcall_id" : shipcall.id}, buffered=False) # for record in participants: participant_query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?"; for record in commands.query(participant_query, model=dict, param={"shipcall_id" : shipcall.id}, buffered=False): # model.Participant_Assignment = model.Participant_Assignment() pa = model.Participant_Assignment(record["participant_id"], record["type"]) shipcall.participants.append(pa) return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} except Exception as ex: logging.error(traceback.format_exc()) logging.error(ex) print(ex) result = {} result["error_field"] = "call failed" return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} finally: if pooledConnection is not None: pooledConnection.close() def PostShipcalls(schemaModel): """ This function *executes* a post-request for shipcalls. The function is accessible as part of an API route. The common sequence is: a) issue a request to the Flask API b) BreCal.api.shipcalls.PostShipcalls, to verify the incoming request (which includes an authentification guard) c) BreCal.impl.shipcalls.PostShipcalls, to execute the incoming request :param schemaModel: The deserialized dict of the request e.g., { 'ship_id': 1, 'type': 1, 'eta': datetime.datetime(2023, 7, 23, 7, 18, 19), 'voyage': '43B', 'tug_required': False, 'pilot_required': True, 'flags': 0, 'pier_side': False, 'bunkering': True, 'recommended_tugs': 2, 'type_value': 1, 'evaluation_value': 0} } """ # This creates a *new* entry try: pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) # query = SQLQuery.get_shipcall_post(schemaModel) # create_sql_query_shipcall_post(schemaModel) query = "INSERT INTO shipcall (" isNotFirst = False for key in schemaModel.keys(): if key == "id": continue if key == "participants": continue if key == "created": continue if key == "modified": continue if key == "evaluation": continue if key == "evaluation_message": continue if key == "type_value": continue if key == "evaluation_value": continue if isNotFirst: query += "," isNotFirst = True query += key query += ") VALUES (" isNotFirst = False for key in schemaModel.keys(): param_key = key if key == "id": continue if key == "participants": continue if key == "created": continue if key == "modified": continue if key == "evaluation": continue if key == "evaluation_message": continue if key == "type": param_key = "type_value" if key == "type_value": continue if key == "evaluation": param_key = "evaluation_value" if key == "evaluation_value": continue if isNotFirst: query += "," isNotFirst = True query += "?" + param_key + "?" query += ")" commands.execute(query, schemaModel) # lquery = SQLQuery.get_shipcall_post_last_insert_id() # new_id = commands.execute_scalar(lquery) new_id = commands.execute_scalar("select last_insert_id()") shipdata = get_ship_data_for_id(schemaModel["ship_id"]) message = shipdata['name'] if "type_value" in schemaModel: match schemaModel["type_value"]: case 1: message += " [ARRIVAL]" case 2: message += " [DEPARTURE]" case 3: message += " [SHIFTING]" # add participant assignments if we have a list of participants if 'participants' in schemaModel: # pquery = SQLQuery.get_shipcall_post_update_shipcall_participant_map() pquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id, type) VALUES (?shipcall_id?, ?participant_id?, ?type?)" nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 1, ?message?)" # type = 1 is assignment for participant_assignment in schemaModel["participants"]: commands.execute(pquery, param={"shipcall_id" : new_id, "participant_id" : participant_assignment["participant_id"], "type" : participant_assignment["type"]}) commands.execute(nquery, param={"shipcall_id" : new_id, "participant_id" : participant_assignment["participant_id"], "message" : message}) # apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database # evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=new_id) # new_id (last insert id) refers to the shipcall id # save history data # TODO: set ETA properly user_data = check_jwt() # query = SQLQuery.create_sql_query_history_post() query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 1)" commands.execute(query, {"scid" : new_id, "pid" : user_data["participant_id"], "uid" : user_data["id"]}) return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'} except ValidationError as ex: return create_validation_error_response(ex, status_code=400, create_log=True) except Exception as ex: logging.error(traceback.format_exc()) logging.error(ex) print(ex) result = {} result["error_field"] = "call failed" return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} finally: if pooledConnection is not None: pooledConnection.close() def PutShipcalls(schemaModel): """ :param schemaModel: The deserialized dict of the request """ # This updates an *existing* entry try: pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) user_data = check_jwt() # test if object to update is found sentinel = object() theshipcall = commands.query_single_or_default("SELECT * FROM shipcall where id = ?id?", sentinel, param={"id" : schemaModel["id"]}) if theshipcall is sentinel: pooledConnection.close() return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'} was_canceled = theshipcall["canceled"] # query = SQLQuery.get_shipcall_put(schemaModel) query = "UPDATE shipcall SET " isNotFirst = False for key in schemaModel.keys(): param_key = key if key == "id": continue if key == "participants": continue if key == "created": continue if key == "modified": continue if key == "evaluation": continue if key == "evaluation_message": continue if key == "type": param_key = "type_value" if key == "type_value": continue if key == "evaluation": param_key = "evaluation_value" if key == "evaluation_value": continue if isNotFirst: query += ", " isNotFirst = True query += key + " = ?" + param_key + "? " query += "WHERE id = ?id?" affected_rows = commands.execute(query, param=schemaModel) shipdata = get_ship_data_for_id(schemaModel["ship_id"]) message = shipdata['name'] if "type_value" in schemaModel: match schemaModel["type_value"]: case 1: message += " [ARRIVAL]" case 2: message += " [DEPARTURE]" case 3: message += " [SHIFTING]" # pquery = SQLQuery.get_shipcall_participant_map_by_shipcall_id() pquery = "SELECT id, participant_id, type FROM shipcall_participant_map where shipcall_id = ?id?" pdata = commands.query(pquery,param={"id" : schemaModel["id"]}) # existing list of assignments # loop across passed participant ids, creating entries for those not present in pdata existing_notifications = get_notification_for_shipcall_and_type(schemaModel["id"], 1) # type = 1 is assignment for participant_assignment in schemaModel["participants"]: found_participant = False for elem in pdata: if elem["participant_id"] == participant_assignment["participant_id"] and elem["type"] == participant_assignment["type"]: found_participant = True break if not found_participant: # nquery = SQLQuery.get_shipcall_post_update_shipcall_participant_map() spquery = "INSERT INTO shipcall_participant_map (shipcall_id, participant_id, type) VALUES (?shipcall_id?, ?participant_id?, ?type?)" commands.execute(spquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_assignment["participant_id"], "type" : participant_assignment["type"]}) # create a notification but only if there is no existing notification in level 0 found_notification = False for existing_notification in existing_notifications: if existing_notification["participant_id"] == participant_assignment["participant_id"] and existing_notification["level"] == 1: found_notification = True break if not found_notification: nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 1, ?message?)" # type = 1 is assignment commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_assignment["participant_id"], "message" : message}) # loop across existing pdata entries, deleting those not present in participant list for elem in pdata: found_participant = False for participant_assignment in schemaModel["participants"]: if(participant_assignment["participant_id"] == elem["participant_id"] and participant_assignment["type"] == elem["type"]): found_participant = True break; if not found_participant: # dquery = SQLQuery.get_shipcall_participant_map_delete_by_id() dquery = "DELETE FROM shipcall_participant_map WHERE id = ?existing_id?" commands.execute(dquery, param={"existing_id" : elem["id"]}) # TODO: Create un-assignment notification but only if level > 0 else delete existing notification for existing_notification in existing_notifications: if existing_notification["participant_id"] == elem["participant_id"]: if existing_notification["level"] == 0: nquery = "DELETE FROM notification WHERE id = ?nid?" commands.execute(nquery, param={"nid" : existing_notification["id"]}) else: # create un-assignment notification nquery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 5, ?message?)" commands.execute(nquery, param={"shipcall_id" : schemaModel["id"], "participant_id" : elem["participant_id"], "message" : message}) break if schemaModel["canceled"] is not None: if schemaModel["canceled"] and not was_canceled: # create a canceled notification for all currently assigned participants stornoNotificationQuery = "INSERT INTO notification (shipcall_id, participant_id, level, type, message) VALUES (?shipcall_id?, ?participant_id?, 0, 7, ?message?)" for participant_assignment in schemaModel["participants"]: commands.execute(stornoNotificationQuery, param={"shipcall_id" : schemaModel["id"], "participant_id" : participant_assignment["participant_id"], "message" : message}) # save history data # TODO: set ETA properly # query = SQLQuery.create_sql_query_history_put() query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 2)" commands.execute(query, {"scid" : schemaModel["id"], "pid" : user_data["participant_id"], "uid" : user_data["id"]}) return json.dumps({"id" : schemaModel["id"]}), 200 except ValidationError as ex: return create_validation_error_response(ex, status_code=400, create_log=True) except Exception as ex: logging.error(traceback.format_exc()) logging.error(ex) print(ex) result = {} result["error_field"] = "call failed" return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} finally: if pooledConnection is not None: pooledConnection.close()