From 863255d3fbfe36508df15569f2cc64baa1723535 Mon Sep 17 00:00:00 2001 From: Dave Walker Date: Tue, 12 Mar 2024 16:47:35 +0000 Subject: [PATCH] Task list items --- README.md | 1 + docker/ui/Dockerfile | 4 +-- .../Controllers/DronesController.cs | 22 ++++++++++-- .../Controllers/FlightPropertiesController.cs | 18 ++++++++++ .../Controllers/FlightsController.cs | 18 ++++++++++ .../FlightPropertyManagerTests.cs | 32 +++++++++++++++++ .../MaintenanceRecordTypeJsonConverter.cs | 2 ++ .../Interfaces/IFlightPropertyManager.cs | 2 ++ .../Logic/FlightManager.cs | 22 ++++++++++-- .../Logic/FlightPropertyManager.cs | 24 +++++++++++++ src/DroneFlightLog.Mvc/Api/DroneClient.cs | 8 ++--- src/DroneFlightLog.Mvc/Api/FlightClient.cs | 8 ++--- .../Api/FlightPropertyClient.cs | 10 +++--- src/DroneFlightLog.Mvc/Api/LocationClient.cs | 9 +++-- .../Api/MaintenanceRecordClient.cs | 35 +++++++++++++++++-- .../Api/ManufacturerClient.cs | 9 +++-- src/DroneFlightLog.Mvc/Api/ModelClient.cs | 8 ++--- .../Entities/MaintenanceRecord.cs | 30 ++++++++++++++++ .../Entities/MaintenanceRecordType.cs | 9 +++++ src/DroneFlightLog.Mvc/appsettings.json | 4 +++ 20 files changed, 238 insertions(+), 37 deletions(-) create mode 100644 src/DroneFlightLog.Mvc/Entities/MaintenanceRecord.cs create mode 100644 src/DroneFlightLog.Mvc/Entities/MaintenanceRecordType.cs diff --git a/README.md b/README.md index d6f7d6e..71a6675 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The logbook allows for storage and maintenance of the following data: - Operator details - Drones, models and manufacturers - Flights, flight locations and flight properties +- Repair, maintenance and modification records ## Getting Started diff --git a/docker/ui/Dockerfile b/docker/ui/Dockerfile index f8ea32a..85c8d5c 100644 --- a/docker/ui/Dockerfile +++ b/docker/ui/Dockerfile @@ -1,4 +1,4 @@ FROM mcr.microsoft.com/dotnet/core/aspnet:latest -COPY droneflightlog.mvc-1.1.4.0 /opt/droneflightlog.mvc-1.1.4.0 -WORKDIR /opt/droneflightlog.mvc-1.1.4.0/bin +COPY droneflightlog.mvc-1.2.0.0 /opt/droneflightlog.mvc-1.2.0.0 +WORKDIR /opt/droneflightlog.mvc-1.2.0.0/bin ENTRYPOINT [ "./DroneFlightLog.Mvc" ] diff --git a/src/DroneFlightLog.Api/Controllers/DronesController.cs b/src/DroneFlightLog.Api/Controllers/DronesController.cs index 24609b2..8928b6b 100644 --- a/src/DroneFlightLog.Api/Controllers/DronesController.cs +++ b/src/DroneFlightLog.Api/Controllers/DronesController.cs @@ -23,13 +23,31 @@ public DronesController(IDroneFlightLogFactory factory) _factory = factory; } + [HttpGet] + [Route("id")] + public async Task> GetDroneByIdAsync(int id) + { + Drone drone; + + try + { + drone = await _factory.Drones.GetDroneAsync(id); + } + catch (DroneNotFoundException) + { + return NotFound(); + } + + return drone; + } + [HttpGet] [Route("")] public async Task>> GetDronesAsync() { List drones = await _factory.Drones.GetDronesAsync(null).ToListAsync(); - if (!drones.Any()) + if (drones.Count == 0) { return NoContent(); } @@ -43,7 +61,7 @@ public async Task>> GetDronesForModelAsync(int modelId) { List drones = await _factory.Drones.GetDronesAsync(modelId).ToListAsync(); - if (!drones.Any()) + if (drones.Count == 0) { return NoContent(); } diff --git a/src/DroneFlightLog.Api/Controllers/FlightPropertiesController.cs b/src/DroneFlightLog.Api/Controllers/FlightPropertiesController.cs index f1716dc..058d940 100644 --- a/src/DroneFlightLog.Api/Controllers/FlightPropertiesController.cs +++ b/src/DroneFlightLog.Api/Controllers/FlightPropertiesController.cs @@ -23,6 +23,24 @@ public FlightPropertiesController(IDroneFlightLogFactory> GetPropertyAsync(int id) + { + FlightProperty property; + + try + { + property = await _factory.Properties.GetPropertyAsync(id); + } + catch (PropertyNotFoundException) + { + return NotFound(); + } + + return property; + } + [HttpGet] [Route("")] public async Task>> GetPropertiesAsync() diff --git a/src/DroneFlightLog.Api/Controllers/FlightsController.cs b/src/DroneFlightLog.Api/Controllers/FlightsController.cs index 1c2ad9c..58968bf 100644 --- a/src/DroneFlightLog.Api/Controllers/FlightsController.cs +++ b/src/DroneFlightLog.Api/Controllers/FlightsController.cs @@ -27,6 +27,24 @@ public FlightsController(IDroneFlightLogFactory factory _factory = factory; } + [HttpGet] + [Route("{id}")] + public async Task> GetFlightByIdAsync(int id) + { + Flight flight; + + try + { + flight = await _factory.Flights.GetFlightAsync(id); + } + catch (FlightNotFoundException) + { + return NotFound(); + } + + return flight; + } + [HttpGet] [Route("{page}/{pageSize}")] public async Task>> GetFlightsAsync(int page, int pageSize) diff --git a/src/DroneFlightLog.Data.Tests/FlightPropertyManagerTests.cs b/src/DroneFlightLog.Data.Tests/FlightPropertyManagerTests.cs index 99728e5..12bcb67 100644 --- a/src/DroneFlightLog.Data.Tests/FlightPropertyManagerTests.cs +++ b/src/DroneFlightLog.Data.Tests/FlightPropertyManagerTests.cs @@ -118,6 +118,38 @@ public void AddExistingPropertyTest() _factory.Properties.AddProperty(PropertyName, PropertyType, true); } + [TestMethod] + public void GetPropertyTest() + { + var property = _factory.Properties.GetProperty(_propertyId); + Assert.AreEqual(PropertyName, property.Name); + Assert.AreEqual(PropertyType, property.DataType); + Assert.IsTrue(property.IsSingleInstance); + } + + [TestMethod] + public async Task GetPropertyAsyncTest() + { + var property = await _factory.Properties.GetPropertyAsync(_propertyId); + Assert.AreEqual(PropertyName, property.Name); + Assert.AreEqual(PropertyType, property.DataType); + Assert.IsTrue(property.IsSingleInstance); + } + + [TestMethod] + [ExpectedException(typeof(PropertyNotFoundException))] + public void GetPropertyForMissingPropertyTest() + { + _factory.Properties.GetProperty(-1); + } + + [TestMethod] + [ExpectedException(typeof(PropertyNotFoundException))] + public async Task GetPropertyForMissingPropertyAsyncTest() + { + await _factory.Properties.GetPropertyAsync(-1); + } + [TestMethod] public void GetPropertiesTest() { diff --git a/src/DroneFlightLog.Data/Binders/MaintenanceRecordTypeJsonConverter.cs b/src/DroneFlightLog.Data/Binders/MaintenanceRecordTypeJsonConverter.cs index 72a3700..26845f2 100644 --- a/src/DroneFlightLog.Data/Binders/MaintenanceRecordTypeJsonConverter.cs +++ b/src/DroneFlightLog.Data/Binders/MaintenanceRecordTypeJsonConverter.cs @@ -1,10 +1,12 @@ using DroneFlightLog.Data.Entities; using System; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; namespace DroneFlightLog.Data.Binders { + [ExcludeFromCodeCoverage] public class MaintenanceRecordTypeJsonConverter : JsonConverter { public override MaintenanceRecordType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/DroneFlightLog.Data/Interfaces/IFlightPropertyManager.cs b/src/DroneFlightLog.Data/Interfaces/IFlightPropertyManager.cs index 0b0578e..c1d523c 100644 --- a/src/DroneFlightLog.Data/Interfaces/IFlightPropertyManager.cs +++ b/src/DroneFlightLog.Data/Interfaces/IFlightPropertyManager.cs @@ -10,6 +10,8 @@ public interface IFlightPropertyManager Task AddPropertyAsync(string name, FlightPropertyDataType type, bool isSingleInstance); FlightPropertyValue AddPropertyValue(int flightId, int propertyId, object value); Task AddPropertyValueAsync(int flightId, int propertyId, object value); + FlightProperty GetProperty(int propertyId); + Task GetPropertyAsync(int propertyId); IEnumerable GetProperties(); IAsyncEnumerable GetPropertiesAsync(); IEnumerable GetPropertyValues(int flightId); diff --git a/src/DroneFlightLog.Data/Logic/FlightManager.cs b/src/DroneFlightLog.Data/Logic/FlightManager.cs index 1f056ac..9402b47 100644 --- a/src/DroneFlightLog.Data/Logic/FlightManager.cs +++ b/src/DroneFlightLog.Data/Logic/FlightManager.cs @@ -84,7 +84,15 @@ public async Task AddFlightAsync(int operatorId, int droneId, int locati /// public Flight GetFlight(int id) { - Flight flight = _factory.Context.Flights.FirstOrDefault(f => f.Id == id); + Flight flight = _factory.Context + .Flights + .Include(f => f.Drone) + .ThenInclude(d => d.Model) + .ThenInclude(m => m.Manufacturer) + .Include(f => f.Location) + .Include(f => f.Operator) + .ThenInclude(o => o.Address) + .FirstOrDefault(f => f.Id == id); ThrowIfFlightNotFound(flight, id); return flight; } @@ -96,7 +104,15 @@ public Flight GetFlight(int id) /// public async Task GetFlightAsync(int id) { - Flight flight = await _factory.Context.Flights.FirstOrDefaultAsync(f => f.Id == id); + Flight flight = await _factory.Context + .Flights + .Include(f => f.Drone) + .ThenInclude(d => d.Model) + .ThenInclude(m => m.Manufacturer) + .Include(f => f.Location) + .Include(f => f.Operator) + .ThenInclude(o => o.Address) + .FirstOrDefaultAsync(f => f.Id == id); ThrowIfFlightNotFound(flight, id); return flight; } @@ -179,6 +195,7 @@ public IEnumerable FindFlights(int? operatorId, int? droneId, int? locat ((locationId == null) || (locationId == f.LocationId)) && ((start == null) || (f.Start >= start)) && ((end == null) || (f.End <= end))) + .OrderBy(f => f.Start) .Skip((pageNumber - 1) * pageSize) .Take(pageSize); @@ -211,6 +228,7 @@ public IAsyncEnumerable FindFlightsAsync(int? operatorId, int? droneId, ((locationId == null) || (locationId == f.LocationId)) && ((start == null) || (f.Start >= start)) && ((end == null) || (f.End <= end))) + .OrderBy(f => f.Start) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .AsAsyncEnumerable(); diff --git a/src/DroneFlightLog.Data/Logic/FlightPropertyManager.cs b/src/DroneFlightLog.Data/Logic/FlightPropertyManager.cs index 29f557d..f0370cf 100644 --- a/src/DroneFlightLog.Data/Logic/FlightPropertyManager.cs +++ b/src/DroneFlightLog.Data/Logic/FlightPropertyManager.cs @@ -183,6 +183,30 @@ public async Task UpdatePropertyValueAsync(int id, object v return propertyValue; } + /// + /// Return a flight propert definition given its Id + /// + /// + /// + public FlightProperty GetProperty(int propertyId) + { + FlightProperty property = _context.FlightProperties.FirstOrDefault(p => p.Id == propertyId); + ThrowIfPropertyNotFound(property, propertyId); + return property; + } + + /// + /// Return a flight propert definition given its Id + /// + /// + /// + public async Task GetPropertyAsync(int propertyId) + { + FlightProperty property = await _context.FlightProperties.FirstOrDefaultAsync(p => p.Id == propertyId); + ThrowIfPropertyNotFound(property, propertyId); + return property; + } + /// /// Get all the current property definitions /// diff --git a/src/DroneFlightLog.Mvc/Api/DroneClient.cs b/src/DroneFlightLog.Mvc/Api/DroneClient.cs index 3d5d510..85fbe5f 100644 --- a/src/DroneFlightLog.Mvc/Api/DroneClient.cs +++ b/src/DroneFlightLog.Mvc/Api/DroneClient.cs @@ -44,10 +44,10 @@ public async Task> GetDronesAsync() /// public async Task GetDroneAsync(int id) { - // TODO : This needs to be replaced with a call to retrieve a single - // drone by Id. For now, retrieve them all then pick the one required - List drones = await GetDronesAsync(); - Drone drone = drones.First(l => l.Id == id); + string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route; + string route = $"{baseRoute}/{id}"; + string json = await SendDirectAsync(route, null, HttpMethod.Get); + Drone drone = JsonConvert.DeserializeObject(json); return drone; } diff --git a/src/DroneFlightLog.Mvc/Api/FlightClient.cs b/src/DroneFlightLog.Mvc/Api/FlightClient.cs index 6e6d319..1bd0b3a 100644 --- a/src/DroneFlightLog.Mvc/Api/FlightClient.cs +++ b/src/DroneFlightLog.Mvc/Api/FlightClient.cs @@ -28,14 +28,10 @@ public FlightClient(HttpClient client, IOptions settings, IHttpCont /// public async Task GetFlightAsync(int flightId) { - // TODO : This needs to be replaced with a call to retrieve a single flight - // by Id. For now, retrieve an arbitrary large number that will cover them - // all then pick the one required string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route; - string route = $"{baseRoute}/1/1000000"; + string route = $"{baseRoute}/{flightId}"; string json = await SendDirectAsync(route, null, HttpMethod.Get); - List flights = JsonConvert.DeserializeObject>(json); - Flight flight = flights.First(f => f.Id == flightId); + Flight flight = JsonConvert.DeserializeObject(json); return flight; } diff --git a/src/DroneFlightLog.Mvc/Api/FlightPropertyClient.cs b/src/DroneFlightLog.Mvc/Api/FlightPropertyClient.cs index 00b9a27..d56c2e4 100644 --- a/src/DroneFlightLog.Mvc/Api/FlightPropertyClient.cs +++ b/src/DroneFlightLog.Mvc/Api/FlightPropertyClient.cs @@ -45,11 +45,11 @@ public async Task> GetFlightPropertiesAsync() /// /// public async Task GetFlightPropertyAsync(int id) - { - // TODO : This needs to be replaced with a call to retrieve a single - // property by Id. For now, retrieve them all then pick the one required - List poperties = await GetFlightPropertiesAsync(); - FlightProperty property = poperties.First(l => l.Id == id); + { + string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == PropertiesRouteKey).Route; + string route = $"{baseRoute}/{id}"; + string json = await SendDirectAsync(route, null, HttpMethod.Get); + FlightProperty property = JsonConvert.DeserializeObject(json); return property; } diff --git a/src/DroneFlightLog.Mvc/Api/LocationClient.cs b/src/DroneFlightLog.Mvc/Api/LocationClient.cs index e885315..f3d5688 100644 --- a/src/DroneFlightLog.Mvc/Api/LocationClient.cs +++ b/src/DroneFlightLog.Mvc/Api/LocationClient.cs @@ -44,11 +44,10 @@ public async Task> GetLocationsAsync() /// public async Task GetLocationAsync(int id) { - // TODO : This needs to be replaced with a call to retrieve a single - // location by Id. For now, retrieve them all then pick the one - // required - List locations = await GetLocationsAsync(); - Location location = locations.First(l => l.Id == id); + string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route; + string route = $"{baseRoute}/{id}"; + string json = await SendDirectAsync(route, null, HttpMethod.Get); + Location location = JsonConvert.DeserializeObject(json); return location; } diff --git a/src/DroneFlightLog.Mvc/Api/MaintenanceRecordClient.cs b/src/DroneFlightLog.Mvc/Api/MaintenanceRecordClient.cs index 75b23e3..5cd99e8 100644 --- a/src/DroneFlightLog.Mvc/Api/MaintenanceRecordClient.cs +++ b/src/DroneFlightLog.Mvc/Api/MaintenanceRecordClient.cs @@ -1,6 +1,37 @@ -namespace DroneFlightLog.Mvc.Api +using DroneFlightLog.Mvc.Configuration; +using DroneFlightLog.Mvc.Entities; +using DroneFlightLog.Mvc.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace DroneFlightLog.Mvc.Api { - public class MaintenanceRecordClient + public class MaintenanceRecordClient : DroneFlightLogClientBase { + private const string RouteKey = "MaintenanceRecords"; + private const string CacheKey = "MaintenanceRecords"; + + public MaintenanceRecordClient(HttpClient client, IOptions settings, IHttpContextAccessor accessor, ICacheWrapper cache) + : base(client, settings, accessor, cache) + { + } + + /// + /// Retrieve a single maintenance record given its ID + /// + /// + /// + public async Task GetMaintenanceRecordAsync(int recordId) + { + string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route; + string route = $"{baseRoute}/{recordId}"; + string json = await SendDirectAsync(route, null, HttpMethod.Get); + MaintenanceRecord maintenanceRecord = JsonConvert.DeserializeObject(json); + return maintenanceRecord; + } } } diff --git a/src/DroneFlightLog.Mvc/Api/ManufacturerClient.cs b/src/DroneFlightLog.Mvc/Api/ManufacturerClient.cs index 9d5c262..4f82151 100644 --- a/src/DroneFlightLog.Mvc/Api/ManufacturerClient.cs +++ b/src/DroneFlightLog.Mvc/Api/ManufacturerClient.cs @@ -44,11 +44,10 @@ public async Task> GetManufacturersAsync() /// public async Task GetManufacturerAsync(int id) { - // TODO : This needs to be replaced with a call to retrieve a single - // manufacturer by Id. For now, retrieve them all then pick the one - // required - List manufacturers = await GetManufacturersAsync(); - Manufacturer manufacturer = manufacturers.First(m => m.Id == id); + string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route; + string route = $"{baseRoute}/{id}"; + string json = await SendDirectAsync(route, null, HttpMethod.Get); + Manufacturer manufacturer = JsonConvert.DeserializeObject(json); return manufacturer; } diff --git a/src/DroneFlightLog.Mvc/Api/ModelClient.cs b/src/DroneFlightLog.Mvc/Api/ModelClient.cs index b47d9b0..3cf7cba 100644 --- a/src/DroneFlightLog.Mvc/Api/ModelClient.cs +++ b/src/DroneFlightLog.Mvc/Api/ModelClient.cs @@ -44,10 +44,10 @@ public async Task> GetModelsAsync() /// public async Task GetModelAsync(int id) { - // TODO : This needs to be replaced with a call to retrieve a single - // model by Id. For now, retrieve them all then pick the one required - List models = await GetModelsAsync(); - Model model = models.First(m => m.Id == id); + string baseRoute = _settings.Value.ApiRoutes.First(r => r.Name == RouteKey).Route; + string route = $"{baseRoute}/{id}"; + string json = await SendDirectAsync(route, null, HttpMethod.Get); + Model model = JsonConvert.DeserializeObject(json); return model; } diff --git a/src/DroneFlightLog.Mvc/Entities/MaintenanceRecord.cs b/src/DroneFlightLog.Mvc/Entities/MaintenanceRecord.cs new file mode 100644 index 0000000..5e394cc --- /dev/null +++ b/src/DroneFlightLog.Mvc/Entities/MaintenanceRecord.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel; + +namespace DroneFlightLog.Mvc.Entities +{ + public class MaintenanceRecord + { + public int Id { get; set; } + + [DisplayName("Maintainer")] + [Required(ErrorMessage = "You must select a maintainer")] + public int MaintainerId { get; set; } + + [DisplayName("Drone")] + [Required(ErrorMessage = "You must select a drone")] + public int DroneId { get; set; } + + [Required(ErrorMessage = "You must select a work date")] + public DateTime DateCompleted { get; set; } + + [Required(ErrorMessage = "You must select a maintenance type")] + public MaintenanceRecordType RecordType { get; set; } + + [Required(ErrorMessage = "You must select a description")] + public string Description { get; set; } + + public string Notes { get; set; } + } +} diff --git a/src/DroneFlightLog.Mvc/Entities/MaintenanceRecordType.cs b/src/DroneFlightLog.Mvc/Entities/MaintenanceRecordType.cs new file mode 100644 index 0000000..1086ad9 --- /dev/null +++ b/src/DroneFlightLog.Mvc/Entities/MaintenanceRecordType.cs @@ -0,0 +1,9 @@ +namespace DroneFlightLog.Mvc.Entities +{ + public enum MaintenanceRecordType + { + Maintenance, + Modification, + Repair + } +} diff --git a/src/DroneFlightLog.Mvc/appsettings.json b/src/DroneFlightLog.Mvc/appsettings.json index 27b871e..a060313 100644 --- a/src/DroneFlightLog.Mvc/appsettings.json +++ b/src/DroneFlightLog.Mvc/appsettings.json @@ -55,6 +55,10 @@ { "Name": "Maintainers", "Route": "/maintainers" + }, + { + "Name": "MaintenanceRecords", + "Route": "/maintenancerecords" } ], "FlightSearchPageSize": 10,