11"""Query alert information from AeroAPI and present it to a frontend service"""
22import os
33from datetime import datetime
4- from typing import Dict , Any , Union
4+ from typing import Dict , Any , Tuple
55
66import json
77import requests
88from flask import Flask , jsonify , Response , request
99from flask_cors import CORS
1010
1111from sqlalchemy import (exc , create_engine , MetaData , Table ,
12- Column , Integer , Boolean , Text , insert , Date )
12+ Column , Integer , Boolean , Text , insert , Date , DateTime )
13+ from sqlalchemy .sql import func
1314
1415AEROAPI_BASE_URL = "https://aeroapi.flightaware.com/aeroapi"
1516AEROAPI_KEY = os .environ ["AEROAPI_KEY" ]
2425engine = create_engine (
2526 "sqlite+pysqlite:////var/db/aeroapi_alerts/aeroapi_alerts.db" , echo = False , future = True
2627)
28+ # Set journal_mode to WAL to enable reading and writing concurrently
29+ with engine .connect () as conn_wal :
30+ conn_wal .exec_driver_sql ("PRAGMA journal_mode=WAL" )
31+ conn_wal .commit ()
2732
28- # Define table and metadata to insert and create
33+ # Define tables and metadata to insert and create
2934metadata_obj = MetaData ()
35+ # Table for alert configurations
3036aeroapi_alert_configurations = Table (
3137 "aeroapi_alert_configurations" ,
3238 metadata_obj ,
4551 Column ("diverted" , Boolean ),
4652 Column ("filed" , Boolean ),
4753 )
54+ # Table for POSTed alerts
55+ aeroapi_alerts = Table (
56+ "aeroapi_alerts" ,
57+ metadata_obj ,
58+ Column ("id" , Integer , primary_key = True , autoincrement = True ),
59+ Column ("time_alert_received" , DateTime (timezone = True ), server_default = func .now ()), # Store time in UTC that the alert was received
60+ Column ("long_description" , Text ),
61+ Column ("short_description" , Text ),
62+ Column ("summary" , Text ),
63+ Column ("event_code" , Text ),
64+ Column ("alert_id" , Integer ),
65+ Column ("fa_flight_id" , Text ),
66+ Column ("ident" , Text ),
67+ Column ("registration" , Text ),
68+ Column ("aircraft_type" , Text ),
69+ Column ("origin" , Text ),
70+ Column ("destination" , Text )
71+ )
4872
4973
50- def create_table ():
74+ def create_tables ():
5175 """
52- Check if the tables exist, and if they don't create them.
76+ Check if the table(s) exist, and if they don't create them.
5377 Returns None, raises exception if error
5478 """
5579 try :
56- # Create the table if it doesn 't exist
80+ # Create the table(s) if they don 't exist
5781 metadata_obj .create_all (engine )
58- app .logger .info ("Table successfully created (if not already created)" )
82+ app .logger .info ("Table(s) successfully created (if not already created)" )
5983 except exc .SQLAlchemyError as e :
60- # Since creation of table is a critical error, raise exception
61- app .logger .error (f"SQL error occurred during creation of table (CRITICAL - THROWING ERROR): { e } " )
84+ # Since creation of table(s) is a critical error, raise exception
85+ app .logger .error (f"SQL error occurred during creation of table(s) (CRITICAL - THROWING ERROR): { e } " )
6286 raise e
6387
6488
65- def insert_into_db (data_to_insert : Dict [str , Union [ str , int , bool ]] ) -> int :
89+ def insert_into_table (data_to_insert : Dict [str , Any ], table : Table ) -> int :
6690 """
67- Insert object into the database based off of the engine.
68- Assumes data_to_insert has values for all the keys:
69- fa_alert_id, ident, origin, destination, aircraft_type, start_date, end_date.
91+ Insert object into the database based off of the table.
92+ Assumes data_to_insert has values for all the keys
93+ that are in the data_to_insert variable, and also that
94+ table is a valid SQLAlchemy Table variable inside the database.
7095 Returns 0 on success, -1 otherwise
7196 """
7297 try :
7398 with engine .connect () as conn :
74- stmt = insert (aeroapi_alert_configurations )
99+ stmt = insert (table )
75100 conn .execute (stmt , data_to_insert )
76101 conn .commit ()
77-
78- app .logger .info ("Data successfully inserted into table" )
79-
102+ app .logger .info (f"Data successfully inserted into table { table .name } " )
80103 except exc .SQLAlchemyError as e :
81- app .logger .error (f"SQL error occurred during insertion into table: { e } " )
104+ app .logger .error (f"SQL error occurred during insertion into table { table . name } : { e } " )
82105 return - 1
83-
84106 return 0
85107
86108
109+ @app .route ("/post" , methods = ["POST" ])
110+ def handle_alert () -> Tuple [Response , int ]:
111+ """
112+ Function to receive AeroAPI POST requests. Filters the request
113+ and puts the necessary data into the SQL database.
114+ Returns a JSON Response and also the status code in a tuple.
115+ """
116+ # Form response
117+ r_title : str
118+ r_detail : str
119+ r_status : int
120+ data : Dict [str , Any ] = request .json
121+ # Process data by getting things needed
122+ processed_data : Dict [str , Any ]
123+ try :
124+ processed_data = {
125+ "long_description" : data ["long_description" ],
126+ "short_description" : data ["short_description" ],
127+ "summary" : data ["summary" ],
128+ "event_code" : data ["event_code" ],
129+ "alert_id" : data ["alert_id" ],
130+ "fa_flight_id" : data ["flight" ]["fa_flight_id" ],
131+ "ident" : data ["flight" ]["ident" ],
132+ "registration" : data ["flight" ]["registration" ],
133+ "aircraft_type" : data ["flight" ]["aircraft_type" ],
134+ "origin" : data ["flight" ]["origin" ],
135+ "destination" : data ["flight" ]["destination" ],
136+ }
137+
138+ # Check if data was inserted into database properly
139+ if insert_into_table (processed_data , aeroapi_alerts ) == - 1 :
140+ r_title = "Error inserting into SQL Database"
141+ r_detail = "Inserting into the database had an error"
142+ r_status = 500
143+ else :
144+ r_title = "Successful request"
145+ r_detail = "Request processed and stored successfully"
146+ r_status = 200
147+ except KeyError as e :
148+ # If value doesn't exist, do not insert into table and produce error
149+ app .logger .error (f"Alert POST request did not have one or more keys with data. Will process but will return 400: { e } " )
150+ r_title = "Missing info in request"
151+ r_detail = "At least one value to insert in the database is missing in the post request"
152+ r_status = 400
153+
154+ return jsonify ({"title" : r_title , "detail" : r_detail , "status" : r_status }), r_status
155+
156+
87157@app .route ("/create" , methods = ["POST" ])
88158def create_alert () -> Response :
89159 """
@@ -99,7 +169,7 @@ def create_alert() -> Response:
99169 r_description : str = ''
100170 # Process json
101171 content_type = request .headers .get ("Content-Type" )
102- data : Dict [Any ]
172+ data : Dict [str , Any ]
103173
104174 if content_type != "application/json" :
105175 r_description = "Invalid content sent"
@@ -148,7 +218,7 @@ def create_alert() -> Response:
148218 data ["end_date" ] = datetime .strptime (data ["end_date" ], "%Y-%m-%d" )
149219 data ["fa_alert_id" ] = fa_alert_id
150220
151- if insert_into_db (data ) == - 1 :
221+ if insert_into_table (data , aeroapi_alert_configurations ) == - 1 :
152222 r_description = f"Database insertion error, check your database configuration. Alert has still been configured with alert id { r_alert_id } "
153223 else :
154224 r_success = True
@@ -159,5 +229,5 @@ def create_alert() -> Response:
159229
160230if __name__ == "__main__" :
161231 # Create the table if it wasn't created before startup
162- create_table ()
232+ create_tables ()
163233 app .run (host = "0.0.0.0" , port = 5000 , debug = True )
0 commit comments