diff --git a/Controllers/API/APIInterface.cs b/Controllers/API/APIInterface.cs index f15712b..5e2db81 100644 --- a/Controllers/API/APIInterface.cs +++ b/Controllers/API/APIInterface.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text; +using System; using System.Threading.Tasks; using Newtonsoft.Json; using System.Net.Http; @@ -11,7 +12,7 @@ namespace EFB.Controllers.API { private HttpClient HttpClient { get; set; } - public async Task Get(string Endpoint, Dictionary Headers){ + public async Task> Get(string Endpoint, Dictionary Headers){ this.HttpClient = new HttpClient(); @@ -35,23 +36,31 @@ namespace EFB.Controllers.API string resultString = result.Content.ReadAsStringAsync().Result; - return new ResponseModel{ - //Sender should be aware of type T becuase of Generic function - Result = JsonConvert.DeserializeObject(resultString) + //Assess return value (object or raw string) + object response = resultString; + + if (typeof(T) != typeof(string)) + {//If the user requests string for return type + response = JsonConvert.DeserializeObject(resultString); + } + + return new ResponseModel(){ + //Sender should be aware of type T becuase of Generic type + Result = (T)response }; } catch (System.Exception e) { - return new ResponseModel{Error = e.Message}; + return new ResponseModel{Error = e.Message}; } } //Returned in the event No other response has been returned - return new ResponseModel{Error = "Invalid Endpoint - Please try again later"}; + return new ResponseModel{Error = "Invalid Endpoint - Please try again later"}; } - public async Task Post(string Endpoint, Dictionary Headers, HttpContent Body){ + public async Task> Post(string Endpoint, Dictionary Headers, HttpContent Body){ this.HttpClient = new HttpClient(); this.HttpClient.DefaultRequestHeaders.Clear(); @@ -70,17 +79,66 @@ namespace EFB.Controllers.API var pendingResult = this.HttpClient.PostAsync(Endpoint, Body); var result = await pendingResult; string resultString = result.Content.ReadAsStringAsync().Result; + + object response = resultString; - return new ResponseModel{ - //Sender should be aware of type T becuase of Generic function - Result = JsonConvert.DeserializeObject(resultString) + if (typeof(T) != typeof(string)) + {//If the user requests string for return type + response = JsonConvert.DeserializeObject(resultString); + } + + return new ResponseModel(){ + //Sender should be aware of type T becuase of Generic type + Result = (T)response }; }catch(System.Exception e){ - return new ResponseModel{Error = e.Message}; + return new ResponseModel{Error = e.Message}; } } //Returned in the event No other response has been returned - return new ResponseModel{Error = "Invalid Endpoint - Please try again later"}; + return new ResponseModel{Error = "Invalid Endpoint - Please try again later"}; + + } + + + + public async Task> Put(string Endpoint, Dictionary Headers, HttpContent Body){ + + this.HttpClient = new HttpClient(); + this.HttpClient.DefaultRequestHeaders.Clear(); + + if (Headers != null) + { + foreach (var Header in Headers) + { + this.HttpClient.DefaultRequestHeaders.Add(Header.Key, Header.Value); + } + } + + if (Form.FormAuthenticator.ValidateEndpoint(Endpoint)) + { + try{//Try statement to catch errors in the process of making the request + var pendingResult = this.HttpClient.PutAsync(Endpoint, Body); + var result = await pendingResult; + string resultString = result.Content.ReadAsStringAsync().Result; + + object response = resultString; + + if (typeof(T) != typeof(string)) + {//If the user requests string for return type + response = JsonConvert.DeserializeObject(resultString); + } + + return new ResponseModel(){ + //Sender should be aware of type T becuase of Generic type + Result = (T)response + }; + }catch(System.Exception e){ + return new ResponseModel{Error = e.Message}; + } + } + //Returned in the event No other response has been returned + return new ResponseModel{Error = "Invalid Endpoint - Please try again later"}; } diff --git a/Controllers/Form/FormAuthenticator.cs b/Controllers/Form/FormAuthenticator.cs index 28dded6..df42554 100644 --- a/Controllers/Form/FormAuthenticator.cs +++ b/Controllers/Form/FormAuthenticator.cs @@ -37,7 +37,7 @@ namespace EFB.Controllers.Form return false; } - public static bool ValidateCruiseAlt(int CruiseAlt){ + public static bool ValidateCruiseAlt(uint CruiseAlt){ if (CruiseAlt > 0 && CruiseAlt < 50000) { return true; diff --git a/Controllers/RouteController.cs b/Controllers/RouteController.cs index 15f2b84..392fed0 100644 --- a/Controllers/RouteController.cs +++ b/Controllers/RouteController.cs @@ -1,13 +1,18 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; +using System.Threading; +using System.Collections.Generic; +using System.Text; +using System.Net.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; using EFB.Models; +using EFB.Models.JSON; using EFB.Sessions; +using EFB.Controllers.Form; +using EFB.Controllers.API; namespace EFB.Controllers { @@ -23,8 +28,8 @@ namespace EFB.Controllers public IActionResult Index() { //Check the user has a valid login - UserModel User = HttpContext.Session.GetObject("User"); - if (User == null || User.Route != null || User.Token.IsExpired()) + UserModel user = HttpContext.Session.GetObject("User"); + if (user == null || user.UserToken.IsExpired()) { return RedirectToAction("Index", "Home"); } @@ -37,5 +42,140 @@ namespace EFB.Controllers { return View("Error!"); } + + public async Task New(string departure, string arrival, string cruise) + { + UserModel user = HttpContext.Session.GetObject("User"); + if (!(user == null || user.UserToken.IsExpired())) + {//If the user is still authenticated + if (FormAuthenticator.ValidateICAOCode(departure) && FormAuthenticator.ValidateICAOCode(arrival)) + {//If the user has entered valid ICAOs + + uint cruiseAlt; + + if (uint.TryParse(cruise, out cruiseAlt) && FormAuthenticator.ValidateCruiseAlt(cruiseAlt)) + {//If the cruise altitude if within limits. + + //Submit route request... + APIInterface API = new APIInterface(); + + //Prepare data to be send off with request (route) + Dictionary headerData = new Dictionary(); + headerData.Add("Authorization", $"Bearer {user.UserToken.TokenValue}"); + + RouteRequest routeRequest = new RouteRequest() + { + departure = departure, + destination = arrival, + preferredminlevel = cruiseAlt / 1000, + preferredmaxlevel = cruiseAlt / 1000, + }; + StringContent content = new StringContent(JsonConvert.SerializeObject(routeRequest), Encoding.UTF8, "application/json"); + + //Make initial Route Request + var requestRoute = API.Post("https://api.autorouter.aero/v1.0/router", headerData, content); + + ResponseModel responseRoute = await requestRoute; + + if (responseRoute.Error == null) + {//Update User session and add route ID + TokenModel routeToken = new TokenModel() + { + TokenValue = responseRoute.Result.ToString() + }; + + user.RouteToken = routeToken; + HttpContext.Session.SetObject("User", user); + + return await Poll(departure, arrival, cruiseAlt); + + } + + TempData["Error"] = responseRoute.Error; + return RedirectToAction("Index", "Route"); + + } + TempData["Error"] = "Invalid Cruise Altitude"; + TempData["Departure"] = departure; + TempData["Arrival"] = arrival; + return RedirectToAction("Index", "Route"); + + } + TempData["Error"] = "Invalid Departure or Arrival ICAO"; + return RedirectToAction("Index", "Route"); + } + return RedirectToAction("Index", "Home"); + } + + + public async Task Poll(string departure, string arrival, uint cruise) + { + if (HttpContext.Session.GetString("User") != null) + {//If the user is currently logged in + UserModel user = HttpContext.Session.GetObject("User"); + + if (user.RouteToken != null) + {//If the user has a route object (e.g, they have been to the route page) + + //Make calls to the server to fetch route + bool collected = false; + int pollCount = 0; + string routeString = ""; + + APIInterface API = new APIInterface(); + + Dictionary headerData = new Dictionary(); + headerData.Add("Authorization", $"Bearer {user.UserToken.TokenValue}"); + + while (collected == false && pollCount < 3) + { + //Make Polling Request + var pollingRequest = API.Put>($"https://api.autorouter.aero/v1.0/router/{user.RouteToken.TokenValue}/longpoll", headerData, null); + + ResponseModel> responsePoll = await pollingRequest; + + + int routePos = responsePoll.Result.Count - 1; + if (responsePoll.Result[routePos].Command == "solution") + { + collected = true; + routeString = responsePoll.Result[routePos].FlightPlan; + break; + } + + Thread.Sleep(3000); + pollCount++; + + } + + if (collected) + { + //fill in route + string finalRoute = RouteModel.ParseRoute(routeString); + + RouteModel route = RouteModel.StringToRoute(departure, arrival, cruise, finalRoute); + user.Route = route; + HttpContext.Session.SetObject("User", user); + + return RedirectToAction("Index", "Route"); + } + + TempData["Error"] = $"Unable to get route after {pollCount} Attempts!"; + return RedirectToAction("Index", "Route"); + + } + else + { + return RedirectToAction("Index", "Route"); + } + + } + else + { + return RedirectToAction("Index", "Route"); + } + } + + } } \ No newline at end of file diff --git a/Controllers/UserController.cs b/Controllers/UserController.cs index 00a3971..c79bd27 100644 --- a/Controllers/UserController.cs +++ b/Controllers/UserController.cs @@ -47,7 +47,7 @@ namespace EFB.Controllers var request = API.Post("https://api.autorouter.aero/v1.0/oauth2/token", null, content); //Wait for the response to come through - ResponseModel response = await request; + ResponseModel response = await request; if (response.Error != null) { @@ -57,15 +57,15 @@ namespace EFB.Controllers }else{ //Type cast required but we know response will be of known type - LoginResponse login = (LoginResponse)response.Result; + LoginResponse login = response.Result; //Generate User Session if (login.error == null) { UserModel user = new UserModel{ EMail = email, - Token = new TokenModel{ - Token = login.access_token, + UserToken = new TokenModel{ + TokenValue = login.access_token, Expiration = DateTime.UtcNow.AddSeconds(login.expires_in) } }; diff --git a/Models/JSON/PollResponse.cs b/Models/JSON/PollResponse.cs new file mode 100644 index 0000000..3c353e9 --- /dev/null +++ b/Models/JSON/PollResponse.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace EFB.Models.JSON +{ + public class PollResponse + { + [JsonProperty(PropertyName = "cmdname")] + public string Command { get; set; } + + [JsonProperty(PropertyName = "fpl")] + public string FlightPlan { get; set; } + } + +} \ No newline at end of file diff --git a/Models/JSON/RouteRequest.cs b/Models/JSON/RouteRequest.cs new file mode 100644 index 0000000..27cdbc5 --- /dev/null +++ b/Models/JSON/RouteRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace EFB.Models.JSON +{ + public class RouteRequest + { + [JsonProperty] + public string departure { get; set; } + [JsonProperty] + public string destination { get; set; } + [JsonProperty] + public uint preferredminlevel { get; set; } + [JsonProperty] + public uint preferredmaxlevel { get; set; } + [JsonProperty] + public string optimise { get; } = "preferred"; + } +} \ No newline at end of file diff --git a/Models/ResponseModel.cs b/Models/ResponseModel.cs index debfb08..92f2f32 100644 --- a/Models/ResponseModel.cs +++ b/Models/ResponseModel.cs @@ -5,11 +5,10 @@ using System.Threading.Tasks; namespace EFB.Models { - public class ResponseModel + public class ResponseModel { //Object should be of known type from sender - public object Result { get; set; } = null; - + public T Result { get; set; } = default(T); public string Error { get; set; } = null; } diff --git a/Models/Route/IWaypoint.cs b/Models/Route/IWaypoint.cs index 793fa93..1c6944f 100644 --- a/Models/Route/IWaypoint.cs +++ b/Models/Route/IWaypoint.cs @@ -15,5 +15,6 @@ namespace EFB.Models.Route public IWaypoint Next { get; set; } public IWaypoint Previous { get; set; } public bool Visited { get; set; } + } } \ No newline at end of file diff --git a/Models/Route/NavaidModel.cs b/Models/Route/NavaidModel.cs index b353b9e..ed147cd 100644 --- a/Models/Route/NavaidModel.cs +++ b/Models/Route/NavaidModel.cs @@ -17,5 +17,9 @@ namespace EFB.Models.Route public IWaypoint Previous { get; set; } = null; public bool Visited { get; set; } = false; + public NavaidModel(string name, string airway){ + Name = name; + Airway = airway; + } } } \ No newline at end of file diff --git a/Models/Route/WaypointModel.cs b/Models/Route/WaypointModel.cs index ab2e239..1acc0bf 100644 --- a/Models/Route/WaypointModel.cs +++ b/Models/Route/WaypointModel.cs @@ -16,20 +16,10 @@ namespace EFB.Models.Route public IWaypoint Previous { get; set; } = null; public bool Visited { get; set; } - - - - - - - - - - - - - - + public WaypointModel(string name, string airway){ + Name = name; + Airway = airway; + } } } \ No newline at end of file diff --git a/Models/RouteModel.cs b/Models/RouteModel.cs index a0468b1..544aa74 100644 --- a/Models/RouteModel.cs +++ b/Models/RouteModel.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using EFB.Models.Route; +using EFB.Controllers.Form; +using System.Net.Http; namespace EFB.Models { @@ -13,10 +15,93 @@ namespace EFB.Models Route only becomes populated after route is recieved from autorouter API */ - public WaypointModel Departure { get; init; } = null; + public WaypointModel Departure { get; set; } = null; public WaypointModel Arrival { get; set; } = null; public IWaypoint Current { get; set; } = null; public uint Cruise { get; set; } = 0; + public RouteModel(string departure, string departureRoute, string arrival, string arrivalRoute, uint cruise){ + if (FormAuthenticator.ValidateICAOCode(departure)) + { + Departure = new WaypointModel(departure, departureRoute); + } + + if (FormAuthenticator.ValidateICAOCode(arrival)) + { + Arrival = new WaypointModel(arrival, arrivalRoute); + } + + if (FormAuthenticator.ValidateCruiseAlt(cruise)) + { + Cruise = cruise; + } + } + + //Generate a route Object + public static RouteModel StringToRoute(string departure, string arrival, uint cruise, string routeString){ + string[] routeTemp = routeString.Split(" "); + + //Set departure and arrival route + string departureRoute = routeTemp[0]; + string arrivalRoute = routeTemp[routeTemp.Length - 2]; + + RouteModel route = new RouteModel(departure, departureRoute, arrival, arrivalRoute, cruise); + route.Departure.Airway = routeTemp[0]; + + route.Current = route.Departure; + + for (var i = 1; i < routeTemp.Length-1; i+=2) + {//Already used first item, continue itterating over every other item + IWaypoint next; + + //Populate 'next' waypoint + if (routeTemp[i].Length > 3) + {//waypoint Type + next = new WaypointModel(routeTemp[i], routeTemp[i+1]); + }else + {//Navaid Type + next = new NavaidModel(routeTemp[i], routeTemp[i+1]); + } + + next.Previous = route.Current; + route.Current.Next = next; + route.Current = next; + } + + //Connect end of route (linked list) + route.Current.Airway = null; + route.Current.Next = route.Arrival; + route.Arrival.Previous = route.Current; + + route.Current = null; + + return route; + } + + + + //Generate a route String + public static string ParseRoute(string route){ + route.Replace('/', ' '); + var routeArr = route.Split(' '); + + string finalRoute = ""; + + foreach (var item in routeArr) + { + var waypoint = item.Split('/')[0]; + if (waypoint.Length <= 7 && waypoint.Length >= 3 && !waypoint.Contains('-')) + { + finalRoute += $"{waypoint} "; + + if (waypoint.Length == 7 && finalRoute.Length > 8) + break; + + } + } + + return finalRoute; + } } + } \ No newline at end of file diff --git a/Models/TokenModel.cs b/Models/TokenModel.cs index 64ddf38..08faff2 100644 --- a/Models/TokenModel.cs +++ b/Models/TokenModel.cs @@ -10,7 +10,7 @@ namespace EFB.Models /* Auto Router API Token Model */ - public string Token { get; init; } + public string TokenValue { get; init; } public DateTime Expiration { get; init; } public bool IsExpired(){ diff --git a/Models/UserModel.cs b/Models/UserModel.cs index e4e2dce..f8179dd 100644 --- a/Models/UserModel.cs +++ b/Models/UserModel.cs @@ -18,10 +18,11 @@ namespace EFB.Models */ public object Id { get; init; } public string EMail { get; init; } - public TokenModel Token { get; set; } = null; + public TokenModel UserToken { get; set; } = null; //Contains the most recent route generated by the user through the App - public object Route { get; set; } = null; + public RouteModel Route { get; set; } = null; + public TokenModel RouteToken { get; set; } = null; //Contains the most recently stored position of the user in the simulator public object SimPosition { get; set; } = null;