Skip to content

Commit

Permalink
Add 'last seen' details to the flight and aircraft details pages
Browse files Browse the repository at this point in the history
  • Loading branch information
davewalker5 committed Nov 30, 2024
1 parent 5543429 commit 9cdcd9e
Show file tree
Hide file tree
Showing 19 changed files with 145 additions and 54 deletions.
52 changes: 31 additions & 21 deletions src/FlightRecorder.Api/Controllers/SightingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,6 @@ public async Task<ActionResult<List<Sighting>>> GetSightingsByFlightAsync(string
return sightings;
}

[HttpGet]
[Route("flight/{date}/{number}/{pageNumber}/{pageSize}")]
public async Task<ActionResult<List<Sighting>>> GetSightingsByFlightAndDateAsync(string date, string number, int pageNumber, int pageSize)
{
DateTime decodedDate = DateTime.ParseExact(HttpUtility.UrlDecode(date), DateTimeFormat, null);
string decodedNumber = HttpUtility.UrlDecode(number).ToUpper();
List<Sighting> sightings = await _factory.Sightings
.ListAsync(s => (s.Flight.Number == decodedNumber) &&
(s.Date == decodedDate),
pageNumber,
pageSize)
.ToListAsync();

if (!sightings.Any())
{
return NoContent();
}

return sightings;
}

[HttpGet]
[Route("airline/{airlineId}/{pageNumber}/{pageSize}")]
public async Task<ActionResult<List<Sighting>>> GetSightingsByAirlineAsync(int airlineId, int pageNumber, int pageSize)
Expand Down Expand Up @@ -150,6 +129,37 @@ public async Task<ActionResult<Sighting>> GetSightingAsync(int id)

return sighting;
}

[HttpGet]
[Route("recent/flight/{number}")]
public async Task<ActionResult<Sighting>> GetMostRecentFlightSightingAsync(string number)
{
string decodedNumber = HttpUtility.UrlDecode(number).ToUpper();
Sighting sighting = await _factory.Sightings.GetMostRecent(x => x.Flight.Number == decodedNumber);

if (sighting == null)
{
return NoContent();
}

return sighting;
}

[HttpGet]
[Route("recent/aircraft/{registration}")]
public async Task<ActionResult<Sighting>> GetMostRecentAircraftSightingAsync(string registration)
{
string decodedRegistration = HttpUtility.UrlDecode(registration);
Sighting sighting = await _factory.Sightings.GetMostRecent(x => x.Aircraft.Registration == decodedRegistration);

if (sighting == null)
{
return NoContent();
}

return sighting;
}


[HttpPut]
[Route("")]
Expand Down
6 changes: 3 additions & 3 deletions src/FlightRecorder.Api/FlightRecorder.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ReleaseVersion>1.12.0.0</ReleaseVersion>
<FileVersion>1.12.0.0</FileVersion>
<ProductVersion>1.12.0</ProductVersion>
<ReleaseVersion>1.13.0.0</ReleaseVersion>
<FileVersion>1.13.0.0</FileVersion>
<ProductVersion>1.13.0</ProductVersion>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
22 changes: 22 additions & 0 deletions src/FlightRecorder.BusinessLogic/Database/SightingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,28 @@ public IAsyncEnumerable<Sighting> ListAsync(Expression<Func<Sighting, bool>> pre
public async Task<int> CountAsync()
=> await _factory.Context.Sightings.CountAsync();

/// <summary>
/// Return the most recent sighting matching the predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public async Task<Sighting> GetMostRecent(Expression<Func<Sighting, bool>> predicate)
{
List<Sighting> sightings = await _factory.Context.Sightings
.Include(s => s.Location)
.Include(s => s.Flight)
.ThenInclude(f => f.Airline)
.Include(s => s.Aircraft)
.ThenInclude(a => a.Model)
.ThenInclude(m => m.Manufacturer)
.Where(predicate)
.OrderByDescending(x => x.Date)
.Take(1)
.AsAsyncEnumerable()
.ToListAsync();
return sightings.FirstOrDefault();
}

/// <summary>
/// Add a new sighting
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>FlightRecorder.BusinessLogic</PackageId>
<PackageVersion>1.8.0.0</PackageVersion>
<PackageVersion>1.9.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2020, 2021, 2022, 2023, 2024</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -16,7 +16,7 @@
<PackageProjectUrl>https://github.com/davewalker5/FlightRecorderDb</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/FlightRecorder.Data/FlightRecorder.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>FlightRecorder.Data</PackageId>
<PackageVersion>1.8.0.0</PackageVersion>
<PackageVersion>1.9.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2020, 2021, 2022, 2023, 2024</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -16,7 +16,7 @@
<PackageProjectUrl>https://github.com/davewalker5/FlightRecorderDb</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>FlightRecorder.DataExchange</PackageId>
<PackageVersion>1.8.0.0</PackageVersion>
<PackageVersion>1.9.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2020, 2021, 2022, 2023, 2024</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -16,7 +16,7 @@
<PackageProjectUrl>https://github.com/davewalker5/FlightRecorderDb</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/FlightRecorder.Entities/FlightRecorder.Entities.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>FlightRecorder.Entities</PackageId>
<PackageVersion>1.8.0.0</PackageVersion>
<PackageVersion>1.9.0.0</PackageVersion>
<Authors>Dave Walker</Authors>
<Copyright>Copyright (c) Dave Walker 2020, 2021, 2022, 2023, 2024</Copyright>
<Owners>Dave Walker</Owners>
Expand All @@ -16,7 +16,7 @@
<PackageProjectUrl>https://github.com/davewalker5/FlightRecorderDb</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/FlightRecorder.Entities/Interfaces/ISightingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public interface ISightingManager
Task<Sighting> AddAsync(long altitude, DateTime date, long locationId, long flightId, long aircraftId);
Task<Sighting> AddAsync(FlattenedSighting flattened);
Task<Sighting> GetAsync(Expression<Func<Sighting, bool>> predicate);
Task<Sighting> GetMostRecent(Expression<Func<Sighting, bool>> predicate);
IAsyncEnumerable<Sighting> ListAsync(Expression<Func<Sighting, bool>> predicate, int pageNumber, int pageSize);
Task<int> CountAsync();
Task<IAsyncEnumerable<Sighting>> ListByAircraftAsync(string registration, int pageNumber, int pageSize);
Expand Down
6 changes: 3 additions & 3 deletions src/FlightRecorder.Manager/FlightRecorder.Manager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<FileVersion>1.8.0.0</FileVersion>
<ProductVersion>1.8.0.0</ProductVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
<FileVersion>1.9.0.0</FileVersion>
<ProductVersion>1.9.0.0</ProductVersion>
<Configurations>Release;Debug</Configurations>
</PropertyGroup>

Expand Down
28 changes: 19 additions & 9 deletions src/FlightRecorder.Mvc/Api/SightingsSearchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,31 @@ public async Task<List<Sighting>> GetSightingsByFlight(string number, int page,
}

/// <summary>
/// Retrieve sightings for the specified flight on the specified date
/// Get the most recent sighting of a flight
/// </summary>
/// <param name="date"></param>
/// <param name="flightNumber"></param>
/// <param name="page"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
public async Task<List<Sighting>> GetSightingsByFlightAndDate(DateTime date, string flightNumber, int page, int pageSize)
public async Task<Sighting> GetMostRecentFlightSighting(string flightNumber)
{
string dateRouteSegment = date.ToString(Settings.Value.DateTimeFormat);
string baseRoute = Settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route;
string route = $"{baseRoute}/flight/{dateRouteSegment}/{flightNumber}/{page}/{pageSize}";
string route = $"{baseRoute}/recent/flight/{flightNumber}";
string json = await SendDirectAsync(route, null, HttpMethod.Get);
List<Sighting> sightings = JsonConvert.DeserializeObject<List<Sighting>>(json, JsonSettings);
return sightings;
Sighting sighting = JsonConvert.DeserializeObject<Sighting>(json, JsonSettings);
return sighting;
}

/// <summary>
/// Get the most recent sighting of an aircraft
/// </summary>
/// <param name="registration"></param>
/// <returns></returns>
public async Task<Sighting> GetMostRecentAircraftSighting(string registration)
{
string baseRoute = Settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route;
string route = $"{baseRoute}/recent/aircraft/{registration}";
string json = await SendDirectAsync(route, null, HttpMethod.Get);
Sighting sighting = JsonConvert.DeserializeObject<Sighting>(json, JsonSettings);
return sighting;
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/FlightRecorder.Mvc/FlightRecorder.Mvc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ReleaseVersion>1.12.0.0</ReleaseVersion>
<FileVersion>1.12.0.0</FileVersion>
<ProductVersion>1.12.0</ProductVersion>
<ReleaseVersion>1.13.0.0</ReleaseVersion>
<FileVersion>1.13.0.0</FileVersion>
<ProductVersion>1.13.0</ProductVersion>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions src/FlightRecorder.Mvc/Models/AircraftDetailsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class AircraftDetailsViewModel

[DisplayName("New Model")]
public string NewModel { get; set; }
public Sighting MostRecentSighting { get; set; }

public string Action { get; set; }

Expand Down
2 changes: 2 additions & 0 deletions src/FlightRecorder.Mvc/Models/FlightDetailsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class FlightDetailsViewModel
[DisplayName("New Airline")]
public string NewAirline { get; set; }
public bool IsDuplicate { get; set; }
public Sighting MostRecentSighting { get; set; }
public int? SightingId { get; set;}

public string Action { get; set; }
public string AirlineErrorMessage { get; set; }
Expand Down
9 changes: 9 additions & 0 deletions src/FlightRecorder.Mvc/Views/AircraftDetails/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
</div>
<hr />

@if (Model.MostRecentSighting != null)
{
<div class="row">
<div class="col-md-12 message">
<span>This aircraft was last seen from @Model.MostRecentSighting.Location.Name on @Model.MostRecentSighting.Date.ToShortDateString(), flight @Model.MostRecentSighting.Flight.Number</span>
</div>
</div>
}

<div class="row">
<div class="col">
<strong>@Html.LabelFor(m => m.Registration)</strong>
Expand Down
8 changes: 8 additions & 0 deletions src/FlightRecorder.Mvc/Views/FlightDetails/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
</div>
</div>
}
else if (Model.MostRecentSighting != null)
{
<div class="row">
<div class="col-md-12 message">
<span>This flight was last seen from @Model.MostRecentSighting.Location.Name on @Model.MostRecentSighting.Date.ToShortDateString()</span>
</div>
</div>
}

<div class="row">
<div class="col">
Expand Down
13 changes: 8 additions & 5 deletions src/FlightRecorder.Mvc/Wizard/AddSightingWizard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ public async Task<FlightDetailsViewModel> GetFlightDetailsModelAsync(string user
model.Embarkation = flight.Embarkation;
model.Destination = flight.Destination;
model.AirlineId = flight.AirlineId;
}

// See if this is a potential duplicate - only need to return the first page with 1 result to do the
// duplicate check
var duplicates = await _sightingsSearch.GetSightingsByFlightAndDate((DateTime)sighting.Date, sighting.FlightNumber, 1, 1);
model.IsDuplicate = duplicates?.Count > 0;
// Retrive the most recent sighting of this flight and see if this is a duplicate. Note that duplicates
// are not reported when editing an existing sighting
model.MostRecentSighting = await _sightingsSearch.GetMostRecentFlightSighting(sighting.FlightNumber);
model.IsDuplicate = sighting.SightingId > 0 ? false : model.MostRecentSighting?.Date == sighting.Date;
}

return model;
}
Expand Down Expand Up @@ -250,6 +250,9 @@ public async Task<AircraftDetailsViewModel> GetAircraftDetailsModelAsync(string
// Load the models for the aircraft's manufacturer
List<Model> models = await GetModelsAsync(model.ManufacturerId ?? 0);
model.SetModels(models);

// Retrive the most recent sighting of this aircraft
model.MostRecentSighting = await _sightingsSearch.GetMostRecentAircraftSighting(aircraft.Registration);
}

return model;
Expand Down
2 changes: 1 addition & 1 deletion src/FlightRecorder.Mvc/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"AllowedHosts": "*",
"AppSettings": {
"Secret": "e2b6e7fe16ef469d9862d43eb76d00e2802ab769b85848048cc9387743ca2cc38c0f4fd8a0de46798f347bedf676bc31",
"ApiUrl": "https://localhost:5001",
"ApiUrl": "http://localhost:5000",
"ApiDateFormat": "yyyy-MM-dd H:mm:ss",
"ApiRoutes": [
{
Expand Down
2 changes: 1 addition & 1 deletion src/FlightRecorder.Tests/FlightRecorder.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net9.0</TargetFramework>

<IsPackable>false</IsPackable>
<ReleaseVersion>1.8.0.0</ReleaseVersion>
<ReleaseVersion>1.9.0.0</ReleaseVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
25 changes: 25 additions & 0 deletions src/FlightRecorder.Tests/SightingManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,30 @@ public async Task ListByMissingLocation()
.ListByLocationAsync("Missing", 1, 100);
Assert.IsNull(sightings);
}

[TestMethod]
public async Task GetMostRecentFlightSighting()
{
var sighting = await _factory.Sightings.GetMostRecent(x => x.Flight.Number == FlightNumber);
Assert.IsNotNull(sighting);
Assert.AreEqual(FlightNumber, sighting.Flight.Number);
Assert.AreEqual(SightingDate, sighting.Date);
}

[TestMethod]
public async Task GetMostRecentAircraftSighting()
{
var sighting = await _factory.Sightings.GetMostRecent(x => x.Aircraft.Registration == Registration);
Assert.IsNotNull(sighting);
Assert.AreEqual(Registration, sighting.Aircraft.Registration);
Assert.AreEqual(SightingDate, sighting.Date);
}

[TestMethod]
public async Task GetMissingRecentSighting()
{
var sighting = await _factory.Sightings.GetMostRecent(x => x.Flight.Embarkation == "Missing");
Assert.IsNull(sighting);
}
}
}

0 comments on commit 9cdcd9e

Please sign in to comment.