API up and running.

There are still open issues but in principle, it is working
This commit is contained in:
Daniel Schick 2023-06-22 10:56:13 +02:00
parent 20f38fff91
commit 15cc4bf8da
11 changed files with 643 additions and 158 deletions

View File

@ -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": []
}
]
}

View File

@ -1,13 +1,17 @@
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl from .. import impl
import json
bp = Blueprint('berths', __name__) bp = Blueprint('berths', __name__)
@bp.route('/berths', methods=['get']) @bp.route('/berths', methods=['get'])
def GetBerths(): def GetBerths():
token = request.headers.get('Authentication')
if 'Authorization' in request.headers:
token = request.headers.get('Authorization')
# TODO: verify token
return impl.berths.GetBerths(token) return impl.berths.GetBerths(token)
else:
return json.dumps("not authenticated"), 403

View File

@ -1,8 +1,7 @@
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl from .. import impl
import logging
import json
bp = Blueprint('notifications', __name__) bp = Blueprint('notifications', __name__)
@ -10,8 +9,13 @@ bp = Blueprint('notifications', __name__)
@bp.route('/notifications', methods=['get']) @bp.route('/notifications', methods=['get'])
def GetNotifications(): def GetNotifications():
# TODO: verify token
if 'participant_id' in request.args:
options = {} options = {}
options["participant_id"] = request.args.get("participant_id") options["participant_id"] = request.args.get("participant_id")
options["shipcall_id"] = request.args.get("shipcall_id") options["shipcall_id"] = request.args.get("shipcall_id")
return impl.notifications.GetNotifications(options) return impl.notifications.GetNotifications(options)
else:
logging.warning("attempt to load notifications without participant id")
return json.dumps("missing argument"), 400

View File

@ -1,16 +1,18 @@
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl from .. import impl
import json
bp = Blueprint('participant', __name__) bp = Blueprint('participant', __name__)
@bp.route('/participant', methods=['get']) @bp.route('/participant', methods=['get'])
def GetParticipant(): def GetParticipant():
if 'Authorization' in request.headers:
token = request.headers.get('Authorization')
# TODO: verify token
options = {} options = {}
options["user_id"] = request.args.get("user_id") options["user_id"] = request.args.get("user_id")
return impl.participant.GetParticipant(options) return impl.participant.GetParticipant(options)
else:
return json.dumps("not authenticated"), 403

View File

@ -4,33 +4,48 @@ from marshmallow import Schema, fields
from ..schemas import model from ..schemas import model
from .. import impl from .. import impl
import logging
import json
bp = Blueprint('shipcalls', __name__) bp = Blueprint('shipcalls', __name__)
# TODO: verify token
@bp.route('/shipcalls', methods=['get']) @bp.route('/shipcalls', methods=['get'])
def GetShipcalls(): def GetShipcalls():
if 'Authorization' in request.headers:
token = request.headers.get('Authorization')
options = {} options = {}
options["participant_id"] = request.args.get("participant_id") 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']) @bp.route('/shipcalls', methods=['post'])
def PostShipcalls(): 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(loadedModel)
return impl.shipcalls.PostShipcalls(body)
@bp.route('/shipcalls', methods=['put']) @bp.route('/shipcalls', methods=['put'])
def PutShipcalls(): 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(loadedModel)
return impl.shipcalls.PutShipcalls(body)

View File

@ -1,13 +1,13 @@
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model
from .. import impl from .. import impl
import json
bp = Blueprint('ships', __name__) bp = Blueprint('ships', __name__)
@bp.route('/ships', methods=['get']) @bp.route('/ships', methods=['get'])
def GetShips(): def GetShips():
if 'Authentication' in request.headers:
token = request.headers.get('Authentication') token = request.headers.get('Authentication')
return impl.ships.GetShips(token) return impl.ships.GetShips(token)
else:
return json.dumps("not authenticated"), 403

View File

@ -1,8 +1,8 @@
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model from ..schemas import model
from .. import impl from .. import impl
import json
import logging
bp = Blueprint('times', __name__) bp = Blueprint('times', __name__)
@ -19,33 +19,46 @@ def GetTimes():
@bp.route('/times', methods=['post']) @bp.route('/times', methods=['post'])
def PostTimes(): def PostTimes():
schema = model.Times() try:
print (request.is_json) # print (request.is_json)
content = request.get_json(force=True) content = request.get_json(force=True) # force gets us json even if the content-type was wrong
print (content)
# body = parser.parse(schema, request, location='json')
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']) @bp.route('/times', methods=['put'])
def PutTimes(): def PutTimes():
schema = model.Times() try:
print (request.is_json) content = request.get_json(force=True)
content = request.get_json() loadedModel = model.TimesSchema().load(data=content, many=False, partial=True)
print (content)
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(loadedModel)
return impl.times.PutTimes(body)
@bp.route('/times', methods=['delete']) @bp.route('/times', methods=['delete'])
def DeleteTimes(): def DeleteTimes():
# TODO check if I am allowd to delete this thing by deriving the participant from the bearer token
if 'id' in request.args:
options = {} options = {}
options["id"] = request.args.get("id") options["id"] = request.args.get("id")
return impl.times.DeleteTimes(options) return impl.times.DeleteTimes(options)
else:
logging.warning("Times delete missing id argument")
return json.dumps("missing argument"), 400

View File

@ -1,13 +1,18 @@
from flask import Blueprint, request from flask import Blueprint, request
from webargs.flaskparser import parser from webargs.flaskparser import parser
from marshmallow import Schema, fields
from ..schemas import model from ..schemas import model
from .. import impl from .. import impl
import json
import logging
bp = Blueprint('verify', __name__) bp = Blueprint('verify', __name__)
@bp.route('/verify', methods=['get']) @bp.route('/verify', methods=['get'])
def GetVerify(): def GetVerify():
if 'X-Api-Key' in request.headers:
apikey = request.headers.get('X-Api-Key') apikey = request.headers.get('X-Api-Key')
return impl.verify.GetVerify(apikey) return impl.verify.GetVerify(apikey)
else:
logging.warning("call without api key")
return json.dumps("missing api key"), 403

View File

@ -29,28 +29,82 @@ def GetShipcalls(options):
return json.dumps(data, default=model.obj_dict), 200 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 # TODO: Validate the upload data
# All the parameters are present in the options argument
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 # This updates an *existing* entry
# All the parameters are present in the options argument 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

View File

@ -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 # TODO: Validate the upload data
# This creates a *new* entry
try: try:
commands = pydapper.using(local_db.connection_pool) 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: except Exception as ex:
logging.error(ex) logging.error(ex)
print(ex) print(ex)
return json.dumps("call failed"), 500 return json.dumps("call failed"), 500
return 0 def PutTimes(schemaModel):
def PutTimes(body):
""" """
:param body: The parsed body of the request :param schemaModel: The deserialized model of the record to be inserted
""" """
# This updates an *existing* entry # 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): def DeleteTimes(options):
""" """
@ -69,11 +111,16 @@ def DeleteTimes(options):
options["id"] 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 if affected_rows == 1:
# All the parameters are present in the options argument return json.dumps({"id" : options["id"]}), 200
return 400
return json.dumps("no such record"), 404
except Exception as ex:
logging.error(ex)
print(ex)
return json.dumps("call failed"), 500

View File

@ -1,5 +1,6 @@
from marshmallow import Schema, fields from marshmallow import Schema, fields, INCLUDE, ValidationError
from dataclasses import dataclass from marshmallow_dataclass import dataclass
import json import json
import datetime import datetime
@ -17,13 +18,6 @@ class Berth(Schema):
created: datetime created: datetime
modified: 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): class Error(Schema):
message = fields.String(required=True,) message = fields.String(required=True,)
@ -43,14 +37,6 @@ class Notification(Schema):
created: datetime created: datetime
modified: 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 @dataclass
class Participant(Schema): class Participant(Schema):
id: int id: int
@ -62,46 +48,14 @@ class Participant(Schema):
created: datetime created: datetime
modified: 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): class ParticipantList(Participant):
pass pass
@dataclass
class Shipcall(Schema):
def __init__(self):
many = True
pass
id: int class ShipcallSchema(Schema):
ship_id: int def __init__(self):
type: int super().__init__(unknown=None)
eta: datetime pass
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
id = fields.Int() id = fields.Int()
ship_id = fields.Int() ship_id = fields.Int()
@ -125,16 +79,57 @@ class Shipcall(Schema):
created = fields.DateTime() created = fields.DateTime()
modified = 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): class ShipcallId(Schema):
pass pass
@dataclass # this is the way!
class Times(Schema):
class TimesSchema(Schema):
def __init__(self): def __init__(self):
many = True super().__init__(unknown=None)
pass 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 id: int
start_planned: datetime start_planned: datetime
end_planned: datetime end_planned: datetime
@ -143,19 +138,11 @@ class Times(Schema):
end_actual: datetime end_actual: datetime
duration_actual: int duration_actual: int
participant_id: int participant_id: int
shipcall_id: int
created: datetime created: datetime
modified: 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 @dataclass
class Ship(Schema): class Ship(Schema):
@ -168,19 +155,10 @@ class Ship(Schema):
created: datetime created: datetime
modified: 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): class TimesId(Schema):
pass pass
@dataclass
class BerthList(Berth): class BerthList(Berth):
pass pass