This commit is contained in:
scopesorting 2024-06-10 18:19:58 +02:00 committed by GitHub
commit a8dde26a02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 131 additions and 7 deletions

View File

@ -13,6 +13,7 @@ from .api import ships
from .api import login from .api import login
from .api import user from .api import user
from .api import history from .api import history
from .api import latest
from BreCal.brecal_utils.file_handling import get_project_root, ensure_path 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 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(login.bp)
app.register_blueprint(user.bp) app.register_blueprint(user.bp)
app.register_blueprint(history.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') 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)) local_db.initPool(os.path.dirname(app.instance_path))

View File

@ -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

View File

@ -16,3 +16,4 @@ def GetBerths():
return impl.berths.GetBerths(token) return impl.berths.GetBerths(token)
else: else:
return json.dumps("not authenticated"), 403 return json.dumps("not authenticated"), 403

View File

@ -7,7 +7,7 @@ bp = Blueprint('history', __name__)
@bp.route('/history', methods=['get']) @bp.route('/history', methods=['get'])
@auth_guard() # no restriction by role @auth_guard() # no restriction by role
def GetParticipant(): def GetParticipant(): # #TODO: rename? Naming might be a copy-paste typo
if 'Authorization' in request.headers: if 'Authorization' in request.headers:
token = request.headers.get('Authorization') token = request.headers.get('Authorization')

View File

@ -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

View File

@ -16,4 +16,6 @@ def GetNotifications():
return impl.notifications.GetNotifications(options) return impl.notifications.GetNotifications(options)
else: else:
logging.warning("attempt to load notifications without shipcall id") logging.warning("attempt to load notifications without shipcall id")
return json.dumps("missing argument"), 400 return json.dumps("missing argument"), 400

View File

@ -53,3 +53,4 @@ def PutShipcalls():
return json.dumps("bad format"), 400 return json.dumps("bad format"), 400
return impl.shipcalls.PutShipcalls(loadedModel) return impl.shipcalls.PutShipcalls(loadedModel)

View File

@ -66,3 +66,5 @@ def DeleteShip():
return json.dumps("bad format"), 400 return json.dumps("bad format"), 400
return impl.ships.DeleteShip(options) return impl.ships.DeleteShip(options)

View File

@ -67,3 +67,4 @@ def DeleteTimes():
else: else:
logging.warning("Times delete missing id argument") logging.warning("Times delete missing id argument")
return json.dumps("missing argument"), 400 return json.dumps("missing argument"), 400

View File

@ -6,4 +6,4 @@ from . import times
from . import ships from . import ships
from . import login from . import login
from . import user from . import user
from . import history from . import history

View File

@ -1,6 +1,7 @@
import json import json
import logging import logging
import pydapper import pydapper
import datetime
from ..schemas import model from ..schemas import model
from .. import local_db from .. import local_db
@ -14,6 +15,7 @@ def GetBerths(token):
pooledConnection = local_db.getPoolConnection() pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection) 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) 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'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: except Exception as ex:
@ -30,3 +32,5 @@ def GetBerths(token):

View File

@ -37,3 +37,5 @@ def GetHistory(options):
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -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

View File

@ -30,3 +30,4 @@ def GetNotifications(options):
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -2,12 +2,14 @@ import json
import logging import logging
import traceback import traceback
import pydapper import pydapper
import datetime
from ..schemas import model from ..schemas import model
from .. import local_db from .. import local_db
from ..services.auth_guard import check_jwt from ..services.auth_guard import check_jwt
from BreCal.database.update_database import evaluate_shipcall_state from BreCal.database.update_database import evaluate_shipcall_state
from BreCal.api import update_latest_modification_time
def GetShipcalls(options): def GetShipcalls(options):
""" """
@ -140,6 +142,9 @@ def PostShipcalls(schemaModel):
user_data = check_jwt() 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)" 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"]}) 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'} 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)" 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"]}) 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 return json.dumps({"id" : schemaModel["id"]}), 200
except Exception as ex: except Exception as ex:
@ -256,4 +264,3 @@ def PutShipcalls(schemaModel):
finally: finally:
if pooledConnection is not None: if pooledConnection is not None:
pooledConnection.close() pooledConnection.close()

View File

@ -1,9 +1,11 @@
import json import json
import logging import logging
import pydapper import pydapper
import datetime
from ..schemas import model from ..schemas import model
from .. import local_db from .. import local_db
from BreCal.api import update_latest_modification_time
def GetShips(token): def GetShips(token):
""" """
@ -11,10 +13,10 @@ def GetShips(token):
""" """
try: try:
pooledConnection = local_db.getPoolConnection() pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection) 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) 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'} 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) print(ex)
result = {} result = {}
result["message"] = "call failed" result["message"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -2,11 +2,13 @@ import json
import logging import logging
import traceback import traceback
import pydapper import pydapper
import datetime
from ..schemas import model from ..schemas import model
from .. import local_db from .. import local_db
from ..services.auth_guard import check_jwt from ..services.auth_guard import check_jwt
from BreCal.api import update_latest_modification_time
from BreCal.database.update_database import evaluate_shipcall_state from BreCal.database.update_database import evaluate_shipcall_state
def GetTimes(options): def GetTimes(options):
@ -91,6 +93,9 @@ def PostTimes(schemaModel):
user_data = check_jwt() 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)" 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"]}) 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'} 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?" query += "WHERE id = ?id?"
affected_rows = commands.execute(query, param=schemaModel) 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' # 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' 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 # 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'} return json.dumps({"id" : schemaModel["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: 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)" 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"]}) 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: if affected_rows == 1:
return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
@ -201,4 +214,4 @@ def DeleteTimes(options):
finally: finally:
if pooledConnection is not None: if pooledConnection is not None:
pooledConnection.close() pooledConnection.close()