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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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