diff --git a/src/server/BreCal/__init__.py b/src/server/BreCal/__init__.py index eb832f5..90869b3 100644 --- a/src/server/BreCal/__init__.py +++ b/src/server/BreCal/__init__.py @@ -13,6 +13,7 @@ from .api import ships from .api import login from .api import user from .api import history +from .api import latest from BreCal.brecal_utils.file_handling import get_project_root, ensure_path from BreCal.brecal_utils.test_handling import execute_test_with_pytest, execute_coverage_test @@ -61,6 +62,7 @@ def create_app(test_config=None): app.register_blueprint(login.bp) app.register_blueprint(user.bp) app.register_blueprint(history.bp) + app.register_blueprint(latest.bp) logging.basicConfig(filename='brecaldevel.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s') local_db.initPool(os.path.dirname(app.instance_path)) diff --git a/src/server/BreCal/api/__init__.py b/src/server/BreCal/api/__init__.py index e69de29..7b19b24 100644 --- a/src/server/BreCal/api/__init__.py +++ b/src/server/BreCal/api/__init__.py @@ -0,0 +1,35 @@ +import typing +import datetime + +# global dictionary, which informs about the latest change of a given database (e.g., 'berths') +# initialize all values as null (None) +latest_get_request_dict = {database:None for database in ["shipcalls", "ships", "times"]} + +def update_latest_modification_time(key:str, modification_time:datetime.datetime)->None: + """ + This function updates the {latest_get_request_dict} inplace at the specified {key} with the defined {modification_time}, + *if* the time is more recent than the currently stored value + """ + global latest_get_request_dict + + value = latest_get_request_dict.get(key,None) + if value is None: + # when there is no value stored for the key, update the value + latest_get_request_dict[key] = modification_time + else: + # when the modification date is more recent than the stored value, update it + if modification_time > value: + latest_get_request_dict[key] = modification_time + + return + +def get_latest_modification_times()->dict[str,typing.Optional[str]]: + """ + This function returns a dictionary, where each key determines the database name, and each value is either an isoformatted datetime + of the latest modification time, or None. + """ + global latest_get_request_dict + + latest_dict = {k:v.isoformat() if isinstance(v,datetime.datetime) else v for k,v in latest_get_request_dict.items()} + return latest_dict + diff --git a/src/server/BreCal/api/berths.py b/src/server/BreCal/api/berths.py index ba8f4ff..0209851 100644 --- a/src/server/BreCal/api/berths.py +++ b/src/server/BreCal/api/berths.py @@ -16,3 +16,4 @@ def GetBerths(): return impl.berths.GetBerths(token) else: return json.dumps("not authenticated"), 403 + diff --git a/src/server/BreCal/api/history.py b/src/server/BreCal/api/history.py index c1fe5ad..33652a8 100644 --- a/src/server/BreCal/api/history.py +++ b/src/server/BreCal/api/history.py @@ -7,7 +7,7 @@ bp = Blueprint('history', __name__) @bp.route('/history', methods=['get']) @auth_guard() # no restriction by role -def GetParticipant(): +def GetParticipant(): # #TODO: rename? Naming might be a copy-paste typo if 'Authorization' in request.headers: token = request.headers.get('Authorization') diff --git a/src/server/BreCal/api/latest.py b/src/server/BreCal/api/latest.py new file mode 100644 index 0000000..9d34ad5 --- /dev/null +++ b/src/server/BreCal/api/latest.py @@ -0,0 +1,18 @@ +from flask import Blueprint, request +from webargs.flaskparser import parser +from .. import impl +from ..services.auth_guard import auth_guard +import json + +bp = Blueprint('getlatest', __name__) + + +@bp.route('/getlatest', methods=['get']) +@auth_guard() # no restriction by role +def GetLatest(): + """This endpoint verifies the user and executes impl.latest.GetLatest""" + if 'Authorization' in request.headers: + token = request.headers.get('Authorization') + return impl.latest.GetLatest(token) + else: + return json.dumps("not authenticated"), 403 diff --git a/src/server/BreCal/api/notifications.py b/src/server/BreCal/api/notifications.py index 48a2a44..c8225b5 100644 --- a/src/server/BreCal/api/notifications.py +++ b/src/server/BreCal/api/notifications.py @@ -16,4 +16,6 @@ def GetNotifications(): return impl.notifications.GetNotifications(options) else: logging.warning("attempt to load notifications without shipcall id") - return json.dumps("missing argument"), 400 \ No newline at end of file + return json.dumps("missing argument"), 400 + + diff --git a/src/server/BreCal/api/shipcalls.py b/src/server/BreCal/api/shipcalls.py index 396dc93..7d20949 100644 --- a/src/server/BreCal/api/shipcalls.py +++ b/src/server/BreCal/api/shipcalls.py @@ -53,3 +53,4 @@ def PutShipcalls(): return json.dumps("bad format"), 400 return impl.shipcalls.PutShipcalls(loadedModel) + diff --git a/src/server/BreCal/api/ships.py b/src/server/BreCal/api/ships.py index e31147e..e1f36b8 100644 --- a/src/server/BreCal/api/ships.py +++ b/src/server/BreCal/api/ships.py @@ -66,3 +66,5 @@ def DeleteShip(): return json.dumps("bad format"), 400 return impl.ships.DeleteShip(options) + + diff --git a/src/server/BreCal/api/times.py b/src/server/BreCal/api/times.py index 2c90397..9e94a40 100644 --- a/src/server/BreCal/api/times.py +++ b/src/server/BreCal/api/times.py @@ -67,3 +67,4 @@ def DeleteTimes(): else: logging.warning("Times delete missing id argument") return json.dumps("missing argument"), 400 + diff --git a/src/server/BreCal/impl/__init__.py b/src/server/BreCal/impl/__init__.py index 93ec9fc..4577c7f 100644 --- a/src/server/BreCal/impl/__init__.py +++ b/src/server/BreCal/impl/__init__.py @@ -6,4 +6,4 @@ from . import times from . import ships from . import login from . import user -from . import history \ No newline at end of file +from . import history diff --git a/src/server/BreCal/impl/berths.py b/src/server/BreCal/impl/berths.py index 9087856..d020b3f 100644 --- a/src/server/BreCal/impl/berths.py +++ b/src/server/BreCal/impl/berths.py @@ -1,6 +1,7 @@ import json import logging import pydapper +import datetime from ..schemas import model from .. import local_db @@ -14,6 +15,7 @@ def GetBerths(token): pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) data = commands.query("SELECT id, name, `lock`, owner_id, authority_id, created, modified, deleted FROM berth WHERE deleted = 0 ORDER BY name", model=model.Berth) + return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} except Exception as ex: @@ -30,3 +32,5 @@ def GetBerths(token): + + diff --git a/src/server/BreCal/impl/history.py b/src/server/BreCal/impl/history.py index f408001..a592f57 100644 --- a/src/server/BreCal/impl/history.py +++ b/src/server/BreCal/impl/history.py @@ -37,3 +37,5 @@ def GetHistory(options): return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} + + diff --git a/src/server/BreCal/impl/latest.py b/src/server/BreCal/impl/latest.py new file mode 100644 index 0000000..7bfa4b9 --- /dev/null +++ b/src/server/BreCal/impl/latest.py @@ -0,0 +1,32 @@ +import json +import logging +import pydapper +import datetime + +from ..schemas import model +from .. import local_db +from BreCal.api import get_latest_modification_times + +def GetLatest(token): + """ + Returns a dictionary of the latest modification dates within the databases 'ships', 'shipcalls' and 'times'. When there has not yet been a modification, this method returns null. + Creates a dictionary of type dict[str, typing.Optional[datetime.datetime]] + """ + + try: + data = get_latest_modification_times() + return json.dumps(data), 200, {'Content-Type': 'application/json; charset=utf-8'} + + except Exception as ex: + logging.error(ex) + print(ex) + result = {} + result["message"] = "call failed" + return json.dumps(result), 500 + + + + + + + diff --git a/src/server/BreCal/impl/notifications.py b/src/server/BreCal/impl/notifications.py index 67619ab..697b761 100644 --- a/src/server/BreCal/impl/notifications.py +++ b/src/server/BreCal/impl/notifications.py @@ -30,3 +30,4 @@ def GetNotifications(options): return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} + diff --git a/src/server/BreCal/impl/shipcalls.py b/src/server/BreCal/impl/shipcalls.py index 59f9311..3b72c3c 100644 --- a/src/server/BreCal/impl/shipcalls.py +++ b/src/server/BreCal/impl/shipcalls.py @@ -2,12 +2,14 @@ import json import logging import traceback import pydapper +import datetime 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.api import update_latest_modification_time def GetShipcalls(options): """ @@ -140,6 +142,9 @@ def PostShipcalls(schemaModel): user_data = check_jwt() 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"]}) + + # track the latest update in the global dictionary + update_latest_modification_time(key="shipcalls",modification_time=datetime.datetime.now()) # new_id return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'} @@ -243,6 +248,9 @@ def PutShipcalls(schemaModel): 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"]}) + # track the latest update in the global dictionary + update_latest_modification_time(key="shipcalls",modification_time=datetime.datetime.now()) # schemaModel["id"] + return json.dumps({"id" : schemaModel["id"]}), 200 except Exception as ex: @@ -256,4 +264,3 @@ def PutShipcalls(schemaModel): finally: if pooledConnection is not None: pooledConnection.close() - diff --git a/src/server/BreCal/impl/ships.py b/src/server/BreCal/impl/ships.py index b0b0b94..857a096 100644 --- a/src/server/BreCal/impl/ships.py +++ b/src/server/BreCal/impl/ships.py @@ -1,9 +1,11 @@ import json import logging import pydapper +import datetime from ..schemas import model from .. import local_db +from BreCal.api import update_latest_modification_time def GetShips(token): """ @@ -11,10 +13,10 @@ def GetShips(token): """ try: - pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) data = commands.query("SELECT id, name, imo, callsign, participant_id, length, width, is_tug, bollard_pull, eni, created, modified, deleted FROM ship ORDER BY name", model=model.Ship) + update_latest_modification_time(key="ships", modification_time=datetime.datetime.now()) return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} @@ -156,4 +158,5 @@ def DeleteShip(options): print(ex) result = {} result["message"] = "call failed" - return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} \ No newline at end of file + return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} + \ No newline at end of file diff --git a/src/server/BreCal/impl/times.py b/src/server/BreCal/impl/times.py index 1f16574..fa9cb79 100644 --- a/src/server/BreCal/impl/times.py +++ b/src/server/BreCal/impl/times.py @@ -2,11 +2,13 @@ import json import logging import traceback import pydapper +import datetime from ..schemas import model from .. import local_db from ..services.auth_guard import check_jwt +from BreCal.api import update_latest_modification_time from BreCal.database.update_database import evaluate_shipcall_state def GetTimes(options): @@ -91,6 +93,9 @@ def PostTimes(schemaModel): user_data = check_jwt() query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?scid?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 2, 1)" commands.execute(query, {"scid" : schemaModel["shipcall_id"], "pid" : user_data["participant_id"], "uid" : user_data["id"]}) + + # track the latest update in the global dictionary + update_latest_modification_time(key="times", modification_time=datetime.datetime.now()) # new_id return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'} @@ -136,6 +141,7 @@ def PutTimes(schemaModel): query += "WHERE id = ?id?" affected_rows = commands.execute(query, param=schemaModel) + new_id = commands.execute_scalar("select last_insert_id()") # apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database 'shipcall' evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=schemaModel["shipcall_id"]) # every times data object refers to the 'shipcall_id' @@ -151,6 +157,9 @@ def PutTimes(schemaModel): # if affected_rows == 1: # this doesn't work as expected + # track the latest update in the global dictionary + update_latest_modification_time(key="times", modification_time=datetime.datetime.now()) # new_id + return json.dumps({"id" : schemaModel["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'} except Exception as ex: @@ -185,6 +194,10 @@ def DeleteTimes(options): query = "INSERT INTO history (participant_id, shipcall_id, user_id, timestamp, eta, type, operation) VALUES (?pid?, ?shipcall_id?, ?uid?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 2, 3)" commands.execute(query, {"pid" : user_data["participant_id"], "shipcall_id" : shipcall_id, "uid" : user_data["id"]}) + # track the latest update in the global dictionary + ###### could use a query similar to this one: modification_time = commands.execute_scalar("SELECT modified FROM times WHERE id = ?id?", param={"id" : options["id"]}) # clarify: when modified is null, created should be used... + update_latest_modification_time(key="times", modification_time=datetime.datetime.now()) # options["id"] + if affected_rows == 1: return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'} @@ -201,4 +214,4 @@ def DeleteTimes(options): finally: if pooledConnection is not None: - pooledConnection.close() \ No newline at end of file + pooledConnection.close()