From f2ae197d7a65bf6a6d3218e8561d6a6147ac66b8 Mon Sep 17 00:00:00 2001 From: jprochazka Date: Tue, 16 Jul 2024 17:44:33 -0400 Subject: [PATCH] Added aircraft endpoints. --- build/portal/backend/backend/__init__.py | 6 +- .../portal/backend/backend/routes/aircraft.py | 114 +++++++++++ .../portal/backend/backend/routes/flights.py | 2 - .../static/adsb_receiver_api_v1_oas3.yaml | 188 +++++++++++++++++- 4 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 build/portal/backend/backend/routes/aircraft.py diff --git a/build/portal/backend/backend/__init__.py b/build/portal/backend/backend/__init__.py index 89a4102..367bdb6 100644 --- a/build/portal/backend/backend/__init__.py +++ b/build/portal/backend/backend/__init__.py @@ -4,7 +4,7 @@ from flask_apscheduler import APScheduler from flask_jwt_extended import JWTManager from backend.jobs.data_collection import data_collection_job from backend.jobs.maintenance import maintenance_job -from backend.routes.flights import flights +from backend.routes.aircraft import aircraft from backend.routes.blog import blog from backend.routes.flights import flights from backend.routes.links import links @@ -22,6 +22,7 @@ def create_app(): app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=365) jwt = JWTManager(app) + app.register_blueprint(aircraft) app.register_blueprint(blog) app.register_blueprint(flights) app.register_blueprint(links) @@ -30,6 +31,7 @@ def create_app(): app.register_blueprint(tokens) app.register_blueprint(users) + # /API/SCHEDULER app.config["SCHEDULER_API_ENABLED"] = True @@ -40,12 +42,14 @@ def create_app(): scheduler.init_app(app) scheduler.start() + # /API/DOCS @app.route('/api/docs') def get_docs(): return render_template('swaggerui.html') + # INIT_APP from . import db diff --git a/build/portal/backend/backend/routes/aircraft.py b/build/portal/backend/backend/routes/aircraft.py new file mode 100644 index 0000000..eb0adc8 --- /dev/null +++ b/build/portal/backend/backend/routes/aircraft.py @@ -0,0 +1,114 @@ +import logging + +from flask import abort, Blueprint, jsonify, request +from flask_jwt_extended import jwt_required +from backend.db import create_connection + +aircraft = Blueprint('aircraft', __name__) + + +@aircraft.route('/api/aircraft/', methods=['GET']) +def get_aircraft_by_icao(icao): + data=[] + + try: + connection = create_connection() + cursor=connection.cursor() + cursor.execute("SELECT * FROM aircraft WHERE icao = %s", (icao,)) + columns=[x[0] for x in cursor.description] + result=cursor.fetchall() + for result in result: + data.append(dict(zip(columns,result))) + except Exception as ex: + logging.error(f"Error encountered while trying to get aircraft using ICAO {icao}", exc_info=ex) + abort(500, description="Internal Server Error") + finally: + connection.close() + + if not data: + abort(404, description="Not Found") + + return jsonify(data[0]), 200 + +@aircraft.route('/api/aircraft//positions', methods=['GET']) +def get_aircraft_positions(icao): + offset = request.args.get('offset', default=0, type=int) + limit = request.args.get('limit', default=500, type=int) + if offset < 0 or limit < 1 or limit > 1000: + abort(400, description="Bad Request") + + positions=[] + + try: + connection = create_connection() + cursor=connection.cursor() + cursor.execute("SELECT id FROM aircraft WHERE icao = %s", (icao,)) + aircraft_id = cursor.fetchone()[0] + cursor.execute("SELECT * FROM positions WHERE aircraft = %s ORDER BY time LIMIT %s, %s", (aircraft_id, offset, limit)) + columns=[x[0] for x in cursor.description] + result=cursor.fetchall() + for result in result: + positions.append(dict(zip(columns,result))) + except Exception as ex: + logging.error(f"Error encountered while trying to get flight positions for aircraft ICAO {icao}", exc_info=ex) + abort(500, description="Internal Server Error") + finally: + connection.close() + + data={} + data['offset'] = offset + data['limit'] = limit + data['count'] = len(positions) + data['positions'] = positions + + return jsonify(data), 200 + +@aircraft.route('/api/aircraft', methods=['GET']) +def get_aircraft(): + offset = request.args.get('offset', default=0, type=int) + limit = request.args.get('limit', default=50, type=int) + if offset < 0 or limit < 1 or limit > 100: + abort(400, description="Bad Request") + + aircraft_data=[] + + try: + connection = create_connection() + cursor=connection.cursor() + cursor.execute("SELECT * FROM aircraft ORDER BY last_seen DESC, icao LIMIT %s, %s", (offset, limit)) + columns=[x[0] for x in cursor.description] + result=cursor.fetchall() + for result in result: + aircraft_data.append(dict(zip(columns,result))) + except Exception as ex: + logging.error('Error encountered while trying to get aircraft', exc_info=ex) + abort(500, description="Internal Server Error") + finally: + connection.close() + + data={} + data['offset'] = offset + data['limit'] = limit + data['count'] = len(aircraft_data) + data['aircraft'] = aircraft_data + + response = jsonify(data) + response.headers.add('Access-Control-Allow-Origin', '*') + return response, 200 + +@aircraft.route('/api/aircraft/count', methods=['GET']) +def get_aircraft_count(): + try: + connection = create_connection() + cursor=connection.cursor() + cursor.execute("SELECT COUNT(*) FROM aircraft") + count=cursor.fetchone()[0] + except Exception as ex: + logging.error('Error encountered while trying to get aircraft count', exc_info=ex) + abort(500, description="Internal Server Error") + finally: + connection.close() + + response = jsonify(aircraft=count) + response.headers.add('Access-Control-Allow-Origin', '*') + return response, 200 \ No newline at end of file diff --git a/build/portal/backend/backend/routes/flights.py b/build/portal/backend/backend/routes/flights.py index 64a9998..2543d44 100644 --- a/build/portal/backend/backend/routes/flights.py +++ b/build/portal/backend/backend/routes/flights.py @@ -8,7 +8,6 @@ flights = Blueprint('flights', __name__) @flights.route('/api/flight/', methods=['GET']) -@jwt_required() def get_flight(flight): data=[] @@ -32,7 +31,6 @@ def get_flight(flight): return jsonify(data[0]), 200 @flights.route('/api/flight//positions', methods=['GET']) -@jwt_required() def get_flight_positions(flight): offset = request.args.get('offset', default=0, type=int) limit = request.args.get('limit', default=500, type=int) diff --git a/build/portal/backend/backend/static/adsb_receiver_api_v1_oas3.yaml b/build/portal/backend/backend/static/adsb_receiver_api_v1_oas3.yaml index 08291b6..22c665c 100644 --- a/build/portal/backend/backend/static/adsb_receiver_api_v1_oas3.yaml +++ b/build/portal/backend/backend/static/adsb_receiver_api_v1_oas3.yaml @@ -12,6 +12,11 @@ externalDocs: servers: - url: http://127.0.0.1:5000/api tags: + - name: aircraft + description: Access and manage aircraft data + externalDocs: + description: Wiki documentation + url: https://github.com/jprochazka/adsb-receiver/wiki - name: blog description: Access and manage blog data externalDocs: @@ -48,6 +53,141 @@ tags: description: Wiki documentation url: https://github.com/jprochazka/adsb-receiver/wiki paths: + /aircraft/{icao}: + get: + tags: + - aircraft + summary: Get data related to the specified aircraft + description: Returns all data related to the aircraft assigned the specified ICAO + operationId: getAircraftByIcao + parameters: + - name: icao + in: path + description: The ICAO assigned the aircraft in question + required: true + schema: + type: string + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/Aircraft' + '401': + description: Unauthorized + '404': + description: Not Found + '500': + description: Internal Server Error + /aircraft/{icao}/positions: + get: + tags: + - aircraft + summary: Get postion data for an aircraft + description: Returns recorded positions for the specified aircraft assigned the specified ICAO + operationId: getAircraftPositions + parameters: + - name: icao + in: path + description: The ICAO of the aircraft for which the position data is being requested + required: true + schema: + type: string + - name: offset + in: query + description: The number of positions to be skipped before adding the first item to the result + required: false + schema: + type: integer + default: 0 + minimum: 0 + - name: limit + in: query + description: Maximum number of positions to be returned + required: false + schema: + type: integer + default: 500 + minimum: 1 + maximum: 1000 + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/GetAircraftPositionsResponse' + '401': + description: Unauthorized + '400': + description: Bad Request + '500': + description: Internal Server Error + /aircraft: + get: + tags: + - aircraft + summary: Get data related to multiple aircraft + description: Returns data for multiple aircraft ordered by the date they were created + operationId: getAircraft + parameters: + - name: offset + in: query + description: The number of aircraft to be skipped before adding the first item to the result + required: false + schema: + type: integer + default: 0 + minimum: 0 + - name: limit + in: query + description: Maximum number of aircraft to be returned + required: false + schema: + type: integer + default: 50 + minimum: 1 + maximum: 100 + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/GetAircraftResponse' + '401': + description: Unauthorized + '400': + description: Bad Request + '500': + description: Internal Server Error + /aircraft/count: + get: + tags: + - aircraft + summary: Gets the number of tracked aircraft + description: Returns the total number of aircraft currently being tracked + operationId: GetAircraftCount + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + items: + $ref: '#/components/schemas/GetAircraftCountResponse' + '401': + description: Unauthorized + '500': + description: Internal Server Error /blog/post: post: tags: @@ -203,8 +343,6 @@ paths: get: tags: - flights - security: - - bearerAuth: [] summary: Get data related to the specified flight description: Returns all data related to the flight assigned the specified flight operationId: getFlight @@ -234,8 +372,6 @@ paths: get: tags: - flights - security: - - bearerAuth: [] summary: Get postion data for a flight description: Returns recorded positions for the specified flight operationId: getFlightPositions @@ -857,6 +993,50 @@ components: scheme: bearer bearerFormat: JWT schemas: + Aircraft: + type: object + properties: + id: + type: integer + icao: + type: string + first_seen: + type: string + format: date-time + last_seen: + type: string + format: date-time + GetAircraftPositionsResponse: + type: object + properties: + offset: + type: integer + limit: + type: integer + count: + type: integer + positions: + type: array + items: + $ref: '#/components/schemas/Position' + GetAircraftResponse: + type: object + properties: + offset: + type: integer + limit: + type: integer + count: + type: integer + flights: + type: array + items: + $ref: '#/components/schemas/Aircraft' + GetAircraftCountResponse: + type: object + properties: + aircraft: + type: integer BlogPost: type: object properties: