From 489dfc2ed64cbc3c0937f4cc6eb51a7d0bad529a Mon Sep 17 00:00:00 2001 From: Daniel Schick Date: Fri, 13 Sep 2024 17:45:31 +0200 Subject: [PATCH] added port_id and ports endpoint to flask app (no validation yet!), reading works --- misc/BreCalApi.cs | 23 +++++++++++- misc/BreCalApi.yaml | 46 ++++++++++++++--------- src/server/BreCal/__init__.py | 2 + src/server/BreCal/api/berths.py | 8 +++- src/server/BreCal/api/participant.py | 10 ++++- src/server/BreCal/api/ports.py | 24 ++++++++++++ src/server/BreCal/api/shipcalls.py | 6 ++- src/server/BreCal/database/sql_queries.py | 8 ++-- src/server/BreCal/impl/__init__.py | 3 +- src/server/BreCal/impl/berths.py | 13 ++++--- src/server/BreCal/impl/participant.py | 27 ++++++++++--- src/server/BreCal/impl/ports.py | 33 ++++++++++++++++ src/server/BreCal/schemas/model.py | 28 ++++++++++++-- 13 files changed, 189 insertions(+), 42 deletions(-) create mode 100644 src/server/BreCal/api/ports.py create mode 100644 src/server/BreCal/impl/ports.py diff --git a/misc/BreCalApi.cs b/misc/BreCalApi.cs index b7f4ce0..700002d 100644 --- a/misc/BreCalApi.cs +++ b/misc/BreCalApi.cs @@ -1,7 +1,7 @@ //---------------------- // -// Generated REST API Client Code Generator v1.9.8.0 on 12.09.2024 16:39:15 +// Generated REST API Client Code Generator v1.9.8.0 on 13.09.2024 16:04:34 // Using the tool OpenAPI Generator v7.4.0 // //---------------------- @@ -7729,10 +7729,11 @@ namespace BreCalClient.misc.Model /// city. /// a logical combination (bitflag) of possible values. This cannot be encoded in a Enumeration type with discrete values in OpenAPI version < 3.1.. /// Bit-encoded flag array for internal use. + /// ports. /// Readonly field set by the database when participant was created. /// Readonly field set by the database when participant was last modified. /// marks the participant as logically deleted (default to false). - public Participant(int id = default(int), string name = default(string), string street = default(string), string postalCode = default(string), string city = default(string), int type = default(int), int? flags = default(int?), DateTime created = default(DateTime), DateTime? modified = default(DateTime?), bool deleted = false) + public Participant(int id = default(int), string name = default(string), string street = default(string), string postalCode = default(string), string city = default(string), int type = default(int), int? flags = default(int?), List ports = default(List), DateTime created = default(DateTime), DateTime? modified = default(DateTime?), bool deleted = false) { this.Id = id; this.Name = name; @@ -7741,6 +7742,7 @@ namespace BreCalClient.misc.Model this.City = city; this.Type = type; this.Flags = flags; + this.Ports = ports; this.Created = created; this.Modified = modified; this.Deleted = deleted; @@ -7790,6 +7792,12 @@ namespace BreCalClient.misc.Model [DataMember(Name = "flags", EmitDefaultValue = true)] public int? Flags { get; set; } /// + /// Gets or Sets Ports + /// + /// [1,2] + [DataMember(Name = "ports", EmitDefaultValue = true)] + public List Ports { get; set; } + /// /// Readonly field set by the database when participant was created /// /// Readonly field set by the database when participant was created @@ -7825,6 +7833,7 @@ namespace BreCalClient.misc.Model sb.Append(" City: ").Append(City).Append("\n"); sb.Append(" Type: ").Append(Type).Append("\n"); sb.Append(" Flags: ").Append(Flags).Append("\n"); + sb.Append(" Ports: ").Append(Ports).Append("\n"); sb.Append(" Created: ").Append(Created).Append("\n"); sb.Append(" Modified: ").Append(Modified).Append("\n"); sb.Append(" Deleted: ").Append(Deleted).Append("\n"); @@ -7893,6 +7902,12 @@ namespace BreCalClient.misc.Model (this.Flags != null && this.Flags.Equals(input.Flags)) ) && + ( + this.Ports == input.Ports || + this.Ports != null && + input.Ports != null && + this.Ports.SequenceEqual(input.Ports) + ) && ( this.Created == input.Created || (this.Created != null && @@ -7939,6 +7954,10 @@ namespace BreCalClient.misc.Model { hashCode = (hashCode * 59) + this.Flags.GetHashCode(); } + if (this.Ports != null) + { + hashCode = (hashCode * 59) + this.Ports.GetHashCode(); + } if (this.Created != null) { hashCode = (hashCode * 59) + this.Created.GetHashCode(); diff --git a/misc/BreCalApi.yaml b/misc/BreCalApi.yaml index 7c86277..5fc9cfb 100644 --- a/misc/BreCalApi.yaml +++ b/misc/BreCalApi.yaml @@ -766,7 +766,7 @@ paths: description: Returns a list of ports tags: - static - operationId: get-ports + operationId: get-ports responses: '200': description: list of ports @@ -780,7 +780,7 @@ paths: country: Germany - id: 4 name: Hamburg - country: Germany + country: Germany '401': $ref: '#/components/responses/401' '403': @@ -789,7 +789,6 @@ paths: $ref: '#/components/responses/500' '503': $ref: '#/components/responses/503' - components: schemas: credentials: @@ -1593,6 +1592,17 @@ components: participant: type: object description: A organisational entity that participates in Bremen Calling + example: + id: 42 + name: BSMD + street: Hermann-Hollerith-Str. 7 + postal code: '28359' + city: Bremen + type: 10 + flags: 0 + created: '2023-08-21T08:23:35Z' + modified: '2023-08-21T08:23:35Z' + deleted: false properties: id: type: integer @@ -1620,8 +1630,21 @@ components: flags: description: Bit-encoded flag array for internal use type: integer - nullable: true example: 0 + nullable: true + ports: + type: array + x-stoplight: + id: cj29rdqeg15b3 + items: + x-stoplight: + id: is7kj0fmlc1nz + type: integer + minimum: 1 + example: 1 + example: + - 1 + - 2 created: type: string format: date-time @@ -1630,25 +1653,14 @@ components: modified: type: string format: date-time - nullable: true description: Readonly field set by the database when participant was last modified example: '2023-08-21T08:23:35Z' + nullable: true deleted: description: marks the participant as logically deleted type: boolean default: false example: false - example: - id: 42 - name: BSMD - street: Hermann-Hollerith-Str. 7 - postal code: '28359' - city: Bremen - type: 10 - flags: 0 - created: '2023-08-21T08:23:35Z' - modified: '2023-08-21T08:23:35Z' - deleted: false participant_list: description: List of participants type: array @@ -1856,7 +1868,7 @@ components: x-stoplight: id: it0cu6ivurgii type: array - items: + items: $ref: '#/components/schemas/port' example: - id: 2 diff --git a/src/server/BreCal/__init__.py b/src/server/BreCal/__init__.py index 263203e..0d2ff39 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 ports 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 @@ -65,6 +66,7 @@ def create_app(test_config=None, instance_path=None): app.register_blueprint(login.bp) app.register_blueprint(user.bp) app.register_blueprint(history.bp) + app.register_blueprint(ports.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/berths.py b/src/server/BreCal/api/berths.py index 114f893..9249dd9 100644 --- a/src/server/BreCal/api/berths.py +++ b/src/server/BreCal/api/berths.py @@ -3,6 +3,7 @@ from flask import Blueprint, request from webargs.flaskparser import parser from .. import impl from ..services.auth_guard import auth_guard +from ..services.jwt_handler import decode_jwt import json from BreCal.validators.validation_error import create_dynamic_exception_response @@ -15,8 +16,11 @@ def GetBerths(): try: if 'Authorization' in request.headers: - token = request.headers.get('Authorization') - return impl.berths.GetBerths(token) + token = request.headers.get('Authorization') + payload = decode_jwt(token.split("Bearer ")[-1]) + options = {} + options["participant_id"] = payload["participant_id"] + return impl.berths.GetBerths(options) else: return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") diff --git a/src/server/BreCal/api/participant.py b/src/server/BreCal/api/participant.py index 814a3dd..475d0f6 100644 --- a/src/server/BreCal/api/participant.py +++ b/src/server/BreCal/api/participant.py @@ -2,6 +2,7 @@ import logging from flask import Blueprint, request from .. import impl from ..services.auth_guard import auth_guard +from ..services.jwt_handler import decode_jwt import json from BreCal.validators.validation_error import create_dynamic_exception_response @@ -12,10 +13,15 @@ bp = Blueprint('participants', __name__) def GetParticipant(): try: - if 'Authorization' in request.headers: - token = request.headers.get('Authorization') + if 'Authorization' in request.headers: + payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1]) options = {} options["user_id"] = request.args.get("user_id") + if "participant_id" in payload: + options["participant_id"] = payload["participant_id"] + else: + return create_dynamic_exception_response(ex=None, status_code=403, message="not authorized") + return impl.participant.GetParticipant(options) else: return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") diff --git a/src/server/BreCal/api/ports.py b/src/server/BreCal/api/ports.py new file mode 100644 index 0000000..6f450d1 --- /dev/null +++ b/src/server/BreCal/api/ports.py @@ -0,0 +1,24 @@ +import logging +from flask import Blueprint, request +from webargs.flaskparser import parser +from .. import impl +from ..services.auth_guard import auth_guard +import json +from BreCal.validators.validation_error import create_dynamic_exception_response + +bp = Blueprint('ports', __name__) + + +@bp.route('/ports', methods=['get']) +@auth_guard() # no restriction by role +def GetPorts(): + + try: + if 'Authorization' in request.headers: + token = request.headers.get('Authorization') + return impl.ports.GetPorts(token) + else: + return create_dynamic_exception_response(ex=None, status_code=403, message="not authenticated") + + except Exception as ex: + return create_dynamic_exception_response(ex=ex, status_code=400) diff --git a/src/server/BreCal/api/shipcalls.py b/src/server/BreCal/api/shipcalls.py index b21908e..b5b8898 100644 --- a/src/server/BreCal/api/shipcalls.py +++ b/src/server/BreCal/api/shipcalls.py @@ -7,6 +7,7 @@ from ..services.auth_guard import auth_guard, check_jwt from BreCal.validators.input_validation import validate_posted_shipcall_data, check_if_user_is_bsmd_type from BreCal.validators.input_validation_shipcall import InputValidationShipcall from BreCal.database.sql_handler import execute_sql_query_standalone +from BreCal.services.jwt_handler import decode_jwt from BreCal.validators.validation_error import create_validation_error_response, create_werkzeug_error_response, create_dynamic_exception_response from . import verify_if_request_is_json @@ -32,9 +33,10 @@ def GetShipcalls(): # oneline: payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1]) """ - options = {} - options["participant_id"] = request.args.get("participant_id") + payload = decode_jwt(request.headers.get("Authorization").split("Bearer ")[-1]) + options = {} options["past_days"] = request.args.get("past_days", default=1, type=int) + options["participant_id"] = payload["participant_id"] return impl.shipcalls.GetShipcalls(options) else: diff --git a/src/server/BreCal/database/sql_queries.py b/src/server/BreCal/database/sql_queries.py index e0e9b7e..5917081 100644 --- a/src/server/BreCal/database/sql_queries.py +++ b/src/server/BreCal/database/sql_queries.py @@ -10,20 +10,22 @@ def create_sql_query_shipcall_get(options:dict)->str: options : dict. A dictionary, which must contains the 'past_days' key (int). Determines the range by which shipcalls are filtered. """ - query = ("SELECT s.id as id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " + + query = ("SELECT s.id as id, ship_id, port_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, " + "flags, s.pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, " + "tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, " + "evaluation_message, evaluation_time, evaluation_notifications_sent, s.created as created, s.modified as modified, time_ref_point " + "FROM shipcall s " + "LEFT JOIN times t ON t.shipcall_id = s.id AND t.participant_type = 8 " + "WHERE " + + "port_id in (SELECT port_id FROM participant_port_map WHERE participant_id = %d)" + + " AND (" + "(type = 1 AND " + "((t.id IS NOT NULL AND t.eta_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " + "(eta >= DATE(NOW() - INTERVAL %d DAY)))) OR " + "((type = 2 OR type = 3) AND " + "((t.id IS NOT NULL AND t.etd_berth >= DATE(NOW() - INTERVAL %d DAY)) OR " + - "(etd >= DATE(NOW() - INTERVAL %d DAY)))) " + - "ORDER BY eta") % (options["past_days"], options["past_days"], options["past_days"], options["past_days"]) + "(etd >= DATE(NOW() - INTERVAL %d DAY))))) " + + "ORDER BY eta") % (options["participant_id"], options["past_days"], options["past_days"], options["past_days"], options["past_days"]) return query diff --git a/src/server/BreCal/impl/__init__.py b/src/server/BreCal/impl/__init__.py index 93ec9fc..8cea758 100644 --- a/src/server/BreCal/impl/__init__.py +++ b/src/server/BreCal/impl/__init__.py @@ -6,4 +6,5 @@ from . import times from . import ships from . import login from . import user -from . import history \ No newline at end of file +from . import history +from . import ports \ No newline at end of file diff --git a/src/server/BreCal/impl/berths.py b/src/server/BreCal/impl/berths.py index 0ed2ca9..aefdd87 100644 --- a/src/server/BreCal/impl/berths.py +++ b/src/server/BreCal/impl/berths.py @@ -4,9 +4,8 @@ import pydapper from ..schemas import model from .. import local_db -from BreCal.database.sql_queries import SQLQuery -def GetBerths(token): +def GetBerths(options): """ No parameters, gets all entries """ @@ -14,9 +13,13 @@ def GetBerths(token): try: pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) - # query = SQLQuery.get_berth() - # data = commands.query(query, 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) + + # only load berths to ports that the participant is assigned to + query = ("SELECT id, name, `lock`, owner_id, port_id, authority_id, created, modified, deleted FROM berth WHERE " + + "deleted = 0 AND + " + "port_id IN (SELECT port_id FROM participant_port_map WHERE participant_id = %d) " + + "ORDER BY name") % (options["participant_id"]) + data = commands.query(query, model=model.Berth) return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} except Exception as ex: diff --git a/src/server/BreCal/impl/participant.py b/src/server/BreCal/impl/participant.py index 7f9cc48..4e38031 100644 --- a/src/server/BreCal/impl/participant.py +++ b/src/server/BreCal/impl/participant.py @@ -10,18 +10,35 @@ def GetParticipant(options): """ :param options: A dictionary containing all the paramters for the Operations options["user_id"]: **Id of user**. *Example: 2*. User id returned by login call. - - """ + """ try: pooledConnection = local_db.getPoolConnection() commands = pydapper.using(pooledConnection) if "user_id" in options and options["user_id"]: # query = SQLQuery.get_participant_by_user_id() - data = commands.query("SELECT p.id as id, p.name as name, p.street as street, p.postal_code as postal_code, p.city as city, p.type as type, p.flags as flags, p.created as created, p.modified as modified, p.deleted as deleted FROM participant p INNER JOIN user u WHERE u.participant_id = p.id and u.id = ?userid?", model=model.Participant, param={"userid" : options["user_id"]}) + query = ("SELECT p.id as id, p.name as name, p.street as street, p.postal_code as postal_code, p.city as city, p.type as type, p.flags as flags, " + + "p.created as created, p.modified as modified, p.deleted as deleted FROM participant p " + + "INNER JOIN user u WHERE u.participant_id = p.id and u.id = %d") % options["user_id"] + data = commands.query(query, model=model.Participant) else: - # query = SQLQuery.get_participants() - data = commands.query("SELECT id, name, street, postal_code, city, type, flags, created, modified, deleted FROM participant p ORDER BY p.name", model=model.Participant) + # query = SQLQuery.get_participants() + + # list only participants that are assigned to the same ports than participant of caller + query = ("SELECT p.id as id, name, street, postal_code, city, type, flags, p.created, p.modified, p.deleted " + + "FROM participant p " + + "JOIN participant_port_map ON p.id = participant_port_map.participant_id " + + "WHERE participant_port_map.port_id IN " + + "(SELECT port_id FROM participant_port_map where participant_id = %d) " + + "GROUP BY id " + + "ORDER BY p.name") % options["participant_id"] + + data = commands.query(query, model=model.Participant) + for participant in data: + port_query = "SELECT port_id FROM participant_port_map WHERE participant_id=?id?"; + for record in commands.query(port_query, model=model.Port_Assignment, param={"id" : participant.id}, buffered=False): + pa = model.Port_Assignment(record.port_id) + participant.ports.append(pa.port_id) return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'} diff --git a/src/server/BreCal/impl/ports.py b/src/server/BreCal/impl/ports.py new file mode 100644 index 0000000..46450e0 --- /dev/null +++ b/src/server/BreCal/impl/ports.py @@ -0,0 +1,33 @@ +import json +import logging +import pydapper + +from ..schemas import model +from .. import local_db +from BreCal.database.sql_queries import SQLQuery + +def GetPorts(token): + """ + No parameters, gets all entries + """ + + try: + pooledConnection = local_db.getPoolConnection() + commands = pydapper.using(pooledConnection) + data = commands.query("SELECT id, name, locode, created, modified, deleted FROM port WHERE deleted = 0 ORDER BY name", model=model.Port) + return json.dumps(data, default=model.obj_dict), 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 + + finally: + if pooledConnection is not None: + pooledConnection.close() + + + + diff --git a/src/server/BreCal/schemas/model.py b/src/server/BreCal/schemas/model.py index c3eed38..e0352a6 100644 --- a/src/server/BreCal/schemas/model.py +++ b/src/server/BreCal/schemas/model.py @@ -29,6 +29,15 @@ class Berth(Schema): lock: bool owner_id: int authority_id: int + port_id: int + created: datetime + modified: datetime + deleted: bool + +class Port(Schema): + id: int + name: str + locode: str created: datetime modified: datetime deleted: bool @@ -164,10 +173,11 @@ class Participant(Schema): postal_code: str city: str type: int # fields.Enum(ParticipantType ...) - flags: int + flags: int created: datetime modified: datetime deleted: bool + ports: List[int] = field(default_factory=list) @validates("type") def validate_type(self, value): @@ -269,6 +279,16 @@ class Participant_Assignment: def to_json(self): return self.__dict__ +@dataclass +class Port_Assignment: + def __init__(self, port_id): + self.port_id = port_id + pass + + port_id: int + + def to_json(self): + return self.__dict__ @dataclass class Shipcall: @@ -301,6 +321,7 @@ class Shipcall: evaluation_time: datetime evaluation_notifications_sent: bool time_ref_point: int + port_id: int created: datetime modified: datetime participants: List[Participant_Assignment] = field(default_factory=list) @@ -335,6 +356,7 @@ class Shipcall: "evaluation_time": self.evaluation_time.isoformat() if self.evaluation_time else "", "evaluation_notifications_sent": self.evaluation_notifications_sent, "time_ref_point": self.time_ref_point, + "port_id": self.port_id, "created": self.created.isoformat() if self.created else "", "modified": self.modified.isoformat() if self.modified else "", "participants": [participant.__dict__ for participant in self.participants] @@ -343,8 +365,8 @@ class Shipcall: @classmethod - def from_query_row(self, id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, evaluation_message, evaluation_time, evaluation_notifications_sent, time_ref_point, created, modified): - return self(id, ship_id, ShipcallType(type), eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, EvaluationType(evaluation), evaluation_message, evaluation_time, evaluation_notifications_sent, time_ref_point, created, modified) + def from_query_row(self, id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, evaluation, evaluation_message, evaluation_time, evaluation_notifications_sent, time_ref_point, port_id, created, modified): + return self(id, ship_id, ShipcallType(type), eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, anchored, moored_lock, canceled, EvaluationType(evaluation), evaluation_message, evaluation_time, evaluation_notifications_sent, time_ref_point, port_id, created, modified) class ShipcallId(Schema): pass