diff --git a/misc/BreCal.postman_collection.json b/misc/BreCal.postman_collection.json new file mode 100644 index 0000000..c91ad79 --- /dev/null +++ b/misc/BreCal.postman_collection.json @@ -0,0 +1,363 @@ +{ + "info": { + "_postman_id": "9242b2d1-196b-4b2e-af57-c0e9eb141dba", + "name": "BreCal", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "10427908" + }, + "item": [ + { + "name": "Participant GET", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://{{PATH}}/participant?user_id=1", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "participant" + ], + "query": [ + { + "key": "user_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "Shipcalls GET", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://{{PATH}}/shipcalls", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "shipcalls" + ] + } + }, + "response": [] + }, + { + "name": "Shipcalls POST", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"ship_id\" : 1,\r\n \"type\" : 1,\r\n \"eta\" : \"2023-07-23T07:18:19\",\r\n \"voyage\" : \"43B\",\r\n \"tug_required\" : false,\r\n \"pilot_required\" : true,\r\n \"flags\" : 0,\r\n \"pier_side\" : false,\r\n \"bunkering\" : true,\r\n \"recommended_tugs\" : 2\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{PATH}}/shipcalls", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "shipcalls" + ] + } + }, + "response": [] + }, + { + "name": "Shipcalls PUT", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\" : 2, \r\n \"recommended_tugs\" : 3\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{PATH}}/shipcalls", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "shipcalls" + ] + } + }, + "response": [] + }, + { + "name": "Berths GET", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://{{PATH}}/berths", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "berths" + ] + } + }, + "response": [] + }, + { + "name": "Notifications GET", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{PATH}}/notifications?participant_id=1", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "notifications" + ], + "query": [ + { + "key": "participant_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "Ships GET", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://{{PATH}}/ships", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "ships" + ] + } + }, + "response": [] + }, + { + "name": "Times GET", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "http://{{PATH}}/times?shipcall_id=1", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "times" + ], + "query": [ + { + "key": "shipcall_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "Times POST", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"start_planned\" : \"2023-04-18T07:18:19\",\r\n \"end_planned\" : \"2023-04-18T09:18:19\", \r\n \"shipcall_id\" : 1,\r\n \"participant_id\" : 1\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{PATH}}/times", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "times" + ] + } + }, + "response": [] + }, + { + "name": "Times PUT", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"start_planned\" : \"2023-05-18T07:18:19\",\r\n \"end_planned\" : \"2023-05-18T09:18:19\", \r\n \"id\" : 1\r\n}" + }, + "url": { + "raw": "http://{{PATH}}/times", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "times" + ] + } + }, + "response": [] + }, + { + "name": "Times DELETE", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "xxxTest", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "http://{{PATH}}/times?id=3", + "protocol": "http", + "host": [ + "{{PATH}}" + ], + "path": [ + "times" + ], + "query": [ + { + "key": "id", + "value": "3" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/src/server/BreCal/api/berths.py b/src/server/BreCal/api/berths.py index c0e0830..70ff383 100644 --- a/src/server/BreCal/api/berths.py +++ b/src/server/BreCal/api/berths.py @@ -1,13 +1,17 @@ from flask import Blueprint, request from webargs.flaskparser import parser -from marshmallow import Schema, fields -from ..schemas import model from .. import impl +import json bp = Blueprint('berths', __name__) @bp.route('/berths', methods=['get']) def GetBerths(): - token = request.headers.get('Authentication') - return impl.berths.GetBerths(token) + + if 'Authorization' in request.headers: + token = request.headers.get('Authorization') + # TODO: verify token + return impl.berths.GetBerths(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 7244730..420d1bc 100644 --- a/src/server/BreCal/api/notifications.py +++ b/src/server/BreCal/api/notifications.py @@ -1,8 +1,7 @@ from flask import Blueprint, request -from webargs.flaskparser import parser -from marshmallow import Schema, fields -from ..schemas import model from .. import impl +import logging +import json bp = Blueprint('notifications', __name__) @@ -10,8 +9,13 @@ bp = Blueprint('notifications', __name__) @bp.route('/notifications', methods=['get']) def GetNotifications(): - options = {} - options["participant_id"] = request.args.get("participant_id") - options["shipcall_id"] = request.args.get("shipcall_id") + # TODO: verify token - return impl.notifications.GetNotifications(options) + if 'participant_id' in request.args: + options = {} + options["participant_id"] = request.args.get("participant_id") + options["shipcall_id"] = request.args.get("shipcall_id") + return impl.notifications.GetNotifications(options) + else: + logging.warning("attempt to load notifications without participant id") + return json.dumps("missing argument"), 400 \ No newline at end of file diff --git a/src/server/BreCal/api/participant.py b/src/server/BreCal/api/participant.py index 85c0810..1566b38 100644 --- a/src/server/BreCal/api/participant.py +++ b/src/server/BreCal/api/participant.py @@ -1,16 +1,18 @@ from flask import Blueprint, request -from webargs.flaskparser import parser -from marshmallow import Schema, fields -from ..schemas import model from .. import impl +import json bp = Blueprint('participant', __name__) - @bp.route('/participant', methods=['get']) def GetParticipant(): - options = {} - options["user_id"] = request.args.get("user_id") + if 'Authorization' in request.headers: + token = request.headers.get('Authorization') + # TODO: verify token + options = {} + options["user_id"] = request.args.get("user_id") + return impl.participant.GetParticipant(options) + else: + return json.dumps("not authenticated"), 403 - return impl.participant.GetParticipant(options) diff --git a/src/server/BreCal/api/shipcalls.py b/src/server/BreCal/api/shipcalls.py index 7f9e865..80ff381 100644 --- a/src/server/BreCal/api/shipcalls.py +++ b/src/server/BreCal/api/shipcalls.py @@ -4,33 +4,48 @@ from marshmallow import Schema, fields from ..schemas import model from .. import impl +import logging +import json + bp = Blueprint('shipcalls', __name__) +# TODO: verify token @bp.route('/shipcalls', methods=['get']) def GetShipcalls(): + if 'Authorization' in request.headers: + token = request.headers.get('Authorization') + options = {} + options["participant_id"] = request.args.get("participant_id") - options = {} - options["participant_id"] = request.args.get("participant_id") - - return impl.shipcalls.GetShipcalls(options) + return impl.shipcalls.GetShipcalls(options) + else: + return json.dumps("not authenticated"), 403 @bp.route('/shipcalls', methods=['post']) def PostShipcalls(): - schema = model.Shipcall() + try: + content = request.get_json(force=True) + loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True) + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("bad format"), 400 - body = parser.parse(schema, request, location='json') - - return impl.shipcalls.PostShipcalls(body) + return impl.shipcalls.PostShipcalls(loadedModel) @bp.route('/shipcalls', methods=['put']) def PutShipcalls(): - schema = model.Shipcall() + try: + content = request.get_json(force=True) + loadedModel = model.ShipcallSchema().load(data=content, many=False, partial=True) + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("bad format"), 400 - body = parser.parse(schema, request, location='json') - - return impl.shipcalls.PutShipcalls(body) + return impl.shipcalls.PutShipcalls(loadedModel) diff --git a/src/server/BreCal/api/ships.py b/src/server/BreCal/api/ships.py index 9c72b83..e4d7958 100644 --- a/src/server/BreCal/api/ships.py +++ b/src/server/BreCal/api/ships.py @@ -1,13 +1,13 @@ from flask import Blueprint, request -from webargs.flaskparser import parser -from marshmallow import Schema, fields -from ..schemas import model from .. import impl +import json bp = Blueprint('ships', __name__) - @bp.route('/ships', methods=['get']) def GetShips(): - token = request.headers.get('Authentication') - return impl.ships.GetShips(token) + if 'Authentication' in request.headers: + token = request.headers.get('Authentication') + return impl.ships.GetShips(token) + else: + return json.dumps("not authenticated"), 403 diff --git a/src/server/BreCal/api/times.py b/src/server/BreCal/api/times.py index e0df7e1..3df4937 100644 --- a/src/server/BreCal/api/times.py +++ b/src/server/BreCal/api/times.py @@ -1,8 +1,8 @@ from flask import Blueprint, request -from webargs.flaskparser import parser -from marshmallow import Schema, fields from ..schemas import model from .. import impl +import json +import logging bp = Blueprint('times', __name__) @@ -19,33 +19,46 @@ def GetTimes(): @bp.route('/times', methods=['post']) def PostTimes(): - schema = model.Times() - print (request.is_json) - content = request.get_json(force=True) - print (content) - # body = parser.parse(schema, request, location='json') + try: + # print (request.is_json) + content = request.get_json(force=True) # force gets us json even if the content-type was wrong - return impl.times.PostTimes(content) + # print (content) + # body = parser.parse(schema, request, location='json') + loadedModel = model.TimesSchema().load(data=content, many=False, partial=True) + + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("bad format"), 400 + + return impl.times.PostTimes(loadedModel) @bp.route('/times', methods=['put']) def PutTimes(): - schema = model.Times() - print (request.is_json) - content = request.get_json() - print (content) + try: + content = request.get_json(force=True) + loadedModel = model.TimesSchema().load(data=content, many=False, partial=True) + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("bad format"), 400 - body = parser.parse(schema, request, location='json') - - return impl.times.PutTimes(body) + return impl.times.PutTimes(loadedModel) @bp.route('/times', methods=['delete']) def DeleteTimes(): - options = {} - options["id"] = request.args.get("id") + # TODO check if I am allowd to delete this thing by deriving the participant from the bearer token - return impl.times.DeleteTimes(options) + if 'id' in request.args: + options = {} + options["id"] = request.args.get("id") + return impl.times.DeleteTimes(options) + else: + logging.warning("Times delete missing id argument") + return json.dumps("missing argument"), 400 diff --git a/src/server/BreCal/api/verify.py b/src/server/BreCal/api/verify.py index c8c400c..f43371b 100644 --- a/src/server/BreCal/api/verify.py +++ b/src/server/BreCal/api/verify.py @@ -1,13 +1,18 @@ from flask import Blueprint, request from webargs.flaskparser import parser -from marshmallow import Schema, fields from ..schemas import model from .. import impl +import json +import logging bp = Blueprint('verify', __name__) @bp.route('/verify', methods=['get']) def GetVerify(): - apikey = request.headers.get('X-Api-Key') - return impl.verify.GetVerify(apikey) + if 'X-Api-Key' in request.headers: + apikey = request.headers.get('X-Api-Key') + return impl.verify.GetVerify(apikey) + else: + logging.warning("call without api key") + return json.dumps("missing api key"), 403 diff --git a/src/server/BreCal/impl/shipcalls.py b/src/server/BreCal/impl/shipcalls.py index 707f95a..0fe4655 100644 --- a/src/server/BreCal/impl/shipcalls.py +++ b/src/server/BreCal/impl/shipcalls.py @@ -29,28 +29,82 @@ def GetShipcalls(options): return json.dumps(data, default=model.obj_dict), 200 -def PostShipcalls(body): +def PostShipcalls(schemaModel): """ - :param body: The parsed body of the request + :param schemaModel: The deserialized dict of the request """ - # Implement your business logic here - # All the parameters are present in the options argument + # TODO: Validate the upload data - return 400 + # This creates a *new* entry + try: + commands = pydapper.using(local_db.connection_pool) + + query = "INSERT INTO shipcall (" + isNotFirst = False + for key in schemaModel.keys(): + if key == "id": + continue + if isNotFirst: + query += "," + isNotFirst = True + query += key + query += ") VALUES (" + isNotFirst = False + for key in schemaModel.keys(): + if key == "id": + continue + if isNotFirst: + query += "," + isNotFirst = True + query += "?" + key + "?" + query += ")" + + commands.execute(query, schemaModel) + + new_id = commands.execute_scalar("select last_insert_id()") + return json.dumps({"id" : new_id}), 201 + + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("call failed"), 500 -def PutShipcalls(body): +def PutShipcalls(schemaModel): """ - :param body: The parsed body of the request + :param schemaModel: The deserialized dict of the request """ - # Implement your business logic here - # All the parameters are present in the options argument + # This updates an *existing* entry + try: + commands = pydapper.using(local_db.connection_pool) - return 400 + query = "UPDATE shipcall SET " + isNotFirst = False + for key in schemaModel.keys(): + if key == "id": + continue + if isNotFirst: + query += ", " + isNotFirst = True + query += key + " = ?" + key + "? " + + query += "WHERE id = ?id?" + + affected_rows = commands.execute(query, param=schemaModel) + + if affected_rows == 1: + return json.dumps({"id" : schemaModel["id"]}), 200 + + return json.dumps("no such record"), 404 + + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("call failed"), 500 diff --git a/src/server/BreCal/impl/times.py b/src/server/BreCal/impl/times.py index feb6b70..6fb68fe 100644 --- a/src/server/BreCal/impl/times.py +++ b/src/server/BreCal/impl/times.py @@ -28,40 +28,82 @@ def GetTimes(options): -def PostTimes(body): +def PostTimes(schemaModel): """ - :param body: The parsed body of the request + :param schemaModel: The deserialized model of the record to be inserted """ - # This creates a *new* entry - # TODO: Validate the upload data + # This creates a *new* entry try: commands = pydapper.using(local_db.connection_pool) + query = "INSERT INTO times (" + isNotFirst = False + for key in schemaModel.keys(): + if key == "id": + continue + if isNotFirst: + query += "," + isNotFirst = True + query += key + query += ") VALUES (" + isNotFirst = False + for key in schemaModel.keys(): + if key == "id": + continue + if isNotFirst: + query += "," + isNotFirst = True + query += "?" + key + "?" + query += ")" + + commands.execute(query, schemaModel) + + new_id = commands.execute_scalar("select last_insert_id()") + return json.dumps({"id" : new_id}), 201 + except Exception as ex: logging.error(ex) print(ex) return json.dumps("call failed"), 500 - return 0 - - -def PutTimes(body): +def PutTimes(schemaModel): """ - :param body: The parsed body of the request + :param schemaModel: The deserialized model of the record to be inserted """ # This updates an *existing* entry + try: + commands = pydapper.using(local_db.connection_pool) + query = "UPDATE times SET " + isNotFirst = False + for key in schemaModel.keys(): + if key == "id": + continue + if isNotFirst: + query += ", " + isNotFirst = True + query += key + " = ?" + key + "? " + query += "WHERE id = ?id?" - return 400 + affected_rows = commands.execute(query, param=schemaModel) + if affected_rows == 1: + return json.dumps({"id" : schemaModel["id"]}), 200 + + return json.dumps("no such record"), 404 + + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("call failed"), 500 def DeleteTimes(options): """ @@ -69,11 +111,16 @@ def DeleteTimes(options): options["id"] """ + try: + commands = pydapper.using(local_db.connection_pool) + affected_rows = commands.execute("DELETE FROM times WHERE id = ?id?", param={"id" : options["id"]}) - # Implement your business logic here - # All the parameters are present in the options argument - - return 400 - + if affected_rows == 1: + return json.dumps({"id" : options["id"]}), 200 + return json.dumps("no such record"), 404 + except Exception as ex: + logging.error(ex) + print(ex) + return json.dumps("call failed"), 500 \ No newline at end of file diff --git a/src/server/BreCal/schemas/model.py b/src/server/BreCal/schemas/model.py index 9cf98e0..4d1f27a 100644 --- a/src/server/BreCal/schemas/model.py +++ b/src/server/BreCal/schemas/model.py @@ -1,5 +1,6 @@ -from marshmallow import Schema, fields -from dataclasses import dataclass +from marshmallow import Schema, fields, INCLUDE, ValidationError +from marshmallow_dataclass import dataclass + import json import datetime @@ -17,13 +18,6 @@ class Berth(Schema): created: datetime modified: datetime - id = fields.Int() - name = fields.String() - participant_id = fields.Int() - lock = fields.Bool() - created = fields.DateTime() - modified = fields.DateTime() - class Error(Schema): message = fields.String(required=True,) @@ -43,14 +37,6 @@ class Notification(Schema): created: datetime modified: datetime - id = fields.Int() - times_id = fields.Int() - acknowledged = fields.Boolean() - level = fields.Int() - type = fields.Int() - created = fields.DateTime() - modified = fields.DateTime() - @dataclass class Participant(Schema): id: int @@ -62,46 +48,14 @@ class Participant(Schema): created: datetime modified: datetime - id = fields.Int() - name = fields.String() - street = fields.String() - postal_code = fields.String() - city = fields.String() - flags = fields.Int() - created = fields.DateTime() - modified = fields.DateTime() - -@dataclass class ParticipantList(Participant): pass -@dataclass -class Shipcall(Schema): - def __init__(self): - many = True - pass - id: int - ship_id: int - type: int - eta: datetime - voyage: str - etd: datetime - arrival_berth_id: int - departure_berth_id: int - tug_required: bool - pilot_required: bool - flags: int - pier_side: bool - bunkering: bool - replenishing: bool - draft: float - tidal_window_from: datetime - tidal_window_to: datetime - rain_sensitive_cargo: bool - recommended_tugs: int - created: datetime - modified: datetime +class ShipcallSchema(Schema): + def __init__(self): + super().__init__(unknown=None) + pass id = fields.Int() ship_id = fields.Int() @@ -125,16 +79,57 @@ class Shipcall(Schema): created = fields.DateTime() modified = fields.DateTime() +@dataclass +class Shipcall: + + id: int + ship_id: int + type: int + eta: datetime + voyage: str + etd: datetime + arrival_berth_id: int + departure_berth_id: int + tug_required: bool + pilot_required: bool + flags: int + pier_side: bool + bunkering: bool + replenishing: bool + draft: float + tidal_window_from: datetime + tidal_window_to: datetime + rain_sensitive_cargo: bool + recommended_tugs: int + created: datetime + modified: datetime + class ShipcallId(Schema): pass -@dataclass -class Times(Schema): +# this is the way! + +class TimesSchema(Schema): def __init__(self): - many = True + super().__init__(unknown=None) pass + id = fields.Int(Required=False) + start_planned = fields.DateTime(Required=False) + end_planned = fields.DateTime(Required = False) + duration_planned = fields.Int(Required = False) + start_actual = fields.DateTime(Required = False) + end_actual = fields.DateTime(Required = False) + duration_actual = fields.Int(Required = False) + participant_id = fields.Int(Required = True) + shipcall_id = fields.Int(Required = True) + created = fields.DateTime(Required = False) + modified = fields.DateTime(Required = False) + +@dataclass +class Times: + id: int start_planned: datetime end_planned: datetime @@ -143,19 +138,11 @@ class Times(Schema): end_actual: datetime duration_actual: int participant_id: int + shipcall_id: int created: datetime modified: datetime - id = fields.Int() - start_planned = fields.DateTime() - end_planned = fields.DateTime() - duration_planned = fields.Int() - start_actual = fields.DateTime() - end_actual = fields.DateTime() - duration_actual = fields.Int() - participant_id = fields.Int() - created = fields.DateTime() - modified = fields.DateTime() + @dataclass class Ship(Schema): @@ -168,19 +155,10 @@ class Ship(Schema): created: datetime modified: datetime - id = fields.Int() - name = fields.Str() - imo = fields.Int() - callsign = fields.Str() - length = fields.Float() - width = fields.Float() - created = fields.DateTime() - modified = fields.DateTime() - class TimesId(Schema): pass -@dataclass + class BerthList(Berth): pass