diff --git a/backend/api/Controllers/MissionSchedulingController.cs b/backend/api/Controllers/MissionSchedulingController.cs index 8320cda94..df3398f2e 100644 --- a/backend/api/Controllers/MissionSchedulingController.cs +++ b/backend/api/Controllers/MissionSchedulingController.cs @@ -2,6 +2,7 @@ using Api.Controllers.Models; using Api.Database.Models; using Api.Services; +using Api.Services.ActionServices; using Api.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,6 +19,7 @@ public class MissionSchedulingController : ControllerBase private readonly IMapService _mapService; private readonly IMissionDefinitionService _missionDefinitionService; private readonly IMissionRunService _missionRunService; + private readonly IMissionSchedulingService _missionSchedulingService; private readonly IRobotService _robotService; private readonly ISourceService _sourceService; private readonly IStidService _stidService; @@ -32,7 +34,8 @@ public MissionSchedulingController( ILogger logger, IMapService mapService, IStidService stidService, - ISourceService sourceService + ISourceService sourceService, + IMissionSchedulingService missionSchedulingService ) { _missionDefinitionService = missionDefinitionService; @@ -44,6 +47,8 @@ ISourceService sourceService _mapService = mapService; _stidService = stidService; _sourceService = sourceService; + _missionDefinitionService = missionDefinitionService; + _missionSchedulingService = missionSchedulingService; _logger = logger; } @@ -194,7 +199,8 @@ [FromBody] ScheduledMissionQuery scheduledMissionQuery { source = new Source { - SourceId = $"{echoMission.Id}", Type = MissionSourceType.Echo + SourceId = $"{echoMission.Id}", + Type = MissionSourceType.Echo }; } else @@ -269,31 +275,23 @@ [FromBody] CustomMissionQuery customMissionQuery ) { var robot = await _robotService.ReadById(customMissionQuery.RobotId); - if (robot is null) - { - return NotFound($"Could not find robot with id {customMissionQuery.RobotId}"); - } + if (robot is null) { return NotFound($"Could not find robot with id {customMissionQuery.RobotId}"); } var installationResults = await _echoService.GetEchoPlantInfos(); - if (installationResults == null) - { - return NotFound("Unable to retrieve plant information from Echo"); - } + if (installationResults == null) { return NotFound("Unable to retrieve plant information from Echo"); } - var installationResult = installationResults.FirstOrDefault(installation => installation.PlantCode.ToUpperInvariant() == customMissionQuery.InstallationCode.ToUpperInvariant()); - if (installationResult == null) - { - return NotFound($"Could not find installation with id {customMissionQuery.InstallationCode}"); - } + var installationResult = + installationResults.FirstOrDefault(installation => installation.PlantCode.ToUpperInvariant() == customMissionQuery.InstallationCode.ToUpperInvariant()); + if (installationResult == null) { return NotFound($"Could not find installation with id {customMissionQuery.InstallationCode}"); } var missionTasks = customMissionQuery.Tasks.Select(task => new MissionTask(task)).ToList(); MissionDefinition? customMissionDefinition; - try { customMissionDefinition = await _missionDefinitionService.FindExistingOrCreateCustomMissionDefinition(customMissionQuery, missionTasks); } + try { customMissionDefinition = await _missionSchedulingService.FindExistingOrCreateCustomMissionDefinition(customMissionQuery, missionTasks); } catch (SourceException e) { return StatusCode(StatusCodes.Status502BadGateway, e.Message); } MissionRun? newMissionRun; - try { newMissionRun = await _customMissionService.QueueCustomMissionRun(customMissionQuery, customMissionDefinition.Id, robot.Id, missionTasks); } + try { newMissionRun = await _missionSchedulingService.QueueCustomMissionRun(customMissionQuery, customMissionDefinition.Id, robot.Id, missionTasks); } catch (Exception e) when (e is RobotNotFoundException or MissionNotFoundException) { return NotFound(e.Message); } return CreatedAtAction(nameof(Create), new diff --git a/backend/api/Program.cs b/backend/api/Program.cs index 003b2b3bc..f1edc89b7 100644 --- a/backend/api/Program.cs +++ b/backend/api/Program.cs @@ -6,6 +6,7 @@ using Api.Mqtt; using Api.Options; using Api.Services; +using Api.Services.ActionServices; using Azure.Identity; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; @@ -37,7 +38,7 @@ } else { - Console.WriteLine($"NO KEYVAULT IN CONFIG"); + Console.WriteLine("NO KEYVAULT IN CONFIG"); } } @@ -62,15 +63,20 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); bool useInMemoryDatabase = builder.Configuration .GetSection("Database") .GetValue("UseInMemoryDatabase"); if (useInMemoryDatabase) +{ builder.Services.AddScoped(); +} else +{ builder.Services.AddScoped(); +} builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/backend/api/Services/ActionServices/MissionSchedulingService.cs b/backend/api/Services/ActionServices/MissionSchedulingService.cs new file mode 100644 index 000000000..0b13f36db --- /dev/null +++ b/backend/api/Services/ActionServices/MissionSchedulingService.cs @@ -0,0 +1,134 @@ +using Api.Controllers.Models; +using Api.Database.Models; +using Api.Utilities; +namespace Api.Services.ActionServices +{ + public interface IMissionSchedulingService + { + public Task FindExistingOrCreateCustomMissionDefinition(CustomMissionQuery customMissionQuery, List missionTasks); + + public Task QueueCustomMissionRun(CustomMissionQuery customMissionQuery, string missionDefinitionId, string robotId, IList missionTasks); + } + + public class MissionSchedulingService : IMissionSchedulingService + { + private readonly IAreaService _areaService; + private readonly ICustomMissionService _customMissionService; + private readonly ILogger _logger; + private readonly IMapService _mapService; + private readonly IMissionDefinitionService _missionDefinitionService; + private readonly IMissionRunService _missionRunService; + private readonly IRobotService _robotService; + private readonly ISourceService _sourceService; + + public MissionSchedulingService( + ILogger logger, + ICustomMissionService customMissionService, + IAreaService areaService, + ISourceService sourceService, + IMissionDefinitionService missionDefinitionService, + IMissionRunService missionRunService, + IRobotService robotService, + IMapService mapService + ) + { + _logger = logger; + _customMissionService = customMissionService; + _areaService = areaService; + _sourceService = sourceService; + _missionDefinitionService = missionDefinitionService; + _missionRunService = missionRunService; + _robotService = robotService; + _mapService = mapService; + } + + public async Task FindExistingOrCreateCustomMissionDefinition(CustomMissionQuery customMissionQuery, List missionTasks) + { + Area? area = null; + if (customMissionQuery.AreaName != null) { area = await _areaService.ReadByInstallationAndName(customMissionQuery.InstallationCode, customMissionQuery.AreaName); } + + var source = await _sourceService.CheckForExistingCustomSource(missionTasks); + + MissionDefinition? existingMissionDefinition = null; + if (source == null) + { + try + { + string sourceUrl = await _customMissionService.UploadSource(missionTasks); + source = new Source + { + SourceId = sourceUrl, + Type = MissionSourceType.Custom + }; + } + catch (Exception e) + { + { + string errorMessage = $"Unable to upload source for mission {customMissionQuery.Name}"; + _logger.LogError(e, "{Message}", errorMessage); + throw new SourceException(errorMessage); + } + } + } + else + { + var missionDefinitions = await _missionDefinitionService.ReadBySourceId(source.SourceId); + if (missionDefinitions.Count > 0) { existingMissionDefinition = missionDefinitions.First(); } + } + + var customMissionDefinition = existingMissionDefinition ?? new MissionDefinition + { + Id = Guid.NewGuid().ToString(), + Source = source, + Name = customMissionQuery.Name, + InspectionFrequency = customMissionQuery.InspectionFrequency, + InstallationCode = customMissionQuery.InstallationCode, + Area = area + }; + + if (existingMissionDefinition == null) { await _missionDefinitionService.Create(customMissionDefinition); } + + return customMissionDefinition; + } + + public async Task QueueCustomMissionRun(CustomMissionQuery customMissionQuery, string missionDefinitionId, string robotId, IList missionTasks) + { + var missionDefinition = await _missionDefinitionService.ReadById(missionDefinitionId); + if (missionDefinition is null) + { + string errorMessage = $"The mission definition with ID {missionDefinition} could not be found"; + _logger.LogError("{Message}", errorMessage); + throw new MissionNotFoundException(errorMessage); + } + + var robot = await _robotService.ReadById(robotId); + if (robot is null) + { + string errorMessage = $"The robot with ID {robotId} could not be found"; + _logger.LogError("{Message}", errorMessage); + throw new RobotNotFoundException(errorMessage); + } + + var scheduledMission = new MissionRun + { + Name = customMissionQuery.Name, + Description = customMissionQuery.Description, + MissionId = missionDefinition.Id, + Comment = customMissionQuery.Comment, + Robot = robot, + Status = MissionStatus.Pending, + DesiredStartTime = customMissionQuery.DesiredStartTime ?? DateTimeOffset.UtcNow, + Tasks = missionTasks, + InstallationCode = customMissionQuery.InstallationCode, + Area = missionDefinition.Area, + Map = new MapMetadata() + }; + + await _mapService.AssignMapToMission(scheduledMission); + + if (scheduledMission.Tasks.Any()) { scheduledMission.CalculateEstimatedDuration(); } + + return await _missionRunService.Create(scheduledMission); + } + } +} diff --git a/backend/api/Services/CustomMissionService.cs b/backend/api/Services/CustomMissionService.cs index eab761c69..56a538239 100644 --- a/backend/api/Services/CustomMissionService.cs +++ b/backend/api/Services/CustomMissionService.cs @@ -1,10 +1,8 @@ using System.Security.Cryptography; using System.Text; using System.Text.Json; -using Api.Controllers.Models; using Api.Database.Models; using Api.Options; -using Api.Utilities; using Microsoft.Extensions.Options; namespace Api.Services { @@ -15,36 +13,18 @@ public interface ICustomMissionService Task?> GetMissionTasksFromSourceId(string id); - public Task QueueCustomMissionRun(CustomMissionQuery customMissionQuery, string missionDefinitionId, string robotId, IList missionTasks); - string CalculateHashFromTasks(IList tasks); } public class CustomMissionService : ICustomMissionService { private readonly IBlobService _blobService; - private readonly ILogger _logger; - private readonly IMapService _mapService; - private readonly IMissionDefinitionService _missionDefinitionService; - private readonly IMissionRunService _missionRunService; - private readonly IRobotService _robotService; private readonly IOptions _storageOptions; - public CustomMissionService( - ILogger logger, - IOptions storageOptions, - IBlobService blobService, - IMissionDefinitionService missionDefinitionService, - IMissionRunService missionRunService, - IRobotService robotService, IMapService mapService) + public CustomMissionService(IOptions storageOptions, IBlobService blobService) { - _logger = logger; _storageOptions = storageOptions; _blobService = blobService; - _missionDefinitionService = missionDefinitionService; - _missionRunService = missionRunService; - _robotService = robotService; - _mapService = mapService; } public async Task UploadSource(List tasks) @@ -90,45 +70,5 @@ public string CalculateHashFromTasks(IList tasks) byte[] hash = SHA256.HashData(Encoding.UTF8.GetBytes(json)); return BitConverter.ToString(hash).Replace("-", "", StringComparison.CurrentCulture).ToUpperInvariant(); } - - public async Task QueueCustomMissionRun(CustomMissionQuery customMissionQuery, string missionDefinitionId, string robotId, IList missionTasks) - { - var missionDefinition = await _missionDefinitionService.ReadById(missionDefinitionId); - if (missionDefinition is null) - { - string errorMessage = $"The mission definition with ID {missionDefinition} could not be found"; - _logger.LogError("{Message}", errorMessage); - throw new MissionNotFoundException(errorMessage); - } - - var robot = await _robotService.ReadById(robotId); - if (robot is null) - { - string errorMessage = $"The robot with ID {robotId} could not be found"; - _logger.LogError("{Message}", errorMessage); - throw new RobotNotFoundException(errorMessage); - } - - var scheduledMission = new MissionRun - { - Name = customMissionQuery.Name, - Description = customMissionQuery.Description, - MissionId = missionDefinition.Id, - Comment = customMissionQuery.Comment, - Robot = robot, - Status = MissionStatus.Pending, - DesiredStartTime = customMissionQuery.DesiredStartTime ?? DateTimeOffset.UtcNow, - Tasks = missionTasks, - InstallationCode = customMissionQuery.InstallationCode, - Area = missionDefinition.Area, - Map = new MapMetadata() - }; - - await _mapService.AssignMapToMission(scheduledMission); - - if (scheduledMission.Tasks.Any()) { scheduledMission.CalculateEstimatedDuration(); } - - return await _missionRunService.Create(scheduledMission); - } } } diff --git a/backend/api/Services/MissionDefinitionService.cs b/backend/api/Services/MissionDefinitionService.cs index 2db1a4696..42f166edc 100644 --- a/backend/api/Services/MissionDefinitionService.cs +++ b/backend/api/Services/MissionDefinitionService.cs @@ -27,8 +27,6 @@ public interface IMissionDefinitionService public Task Update(MissionDefinition missionDefinition); public Task Delete(string id); - - public Task FindExistingOrCreateCustomMissionDefinition(CustomMissionQuery customMissionQuery, List missionTasks); } [SuppressMessage( @@ -48,12 +46,10 @@ public interface IMissionDefinitionService )] public class MissionDefinitionService : IMissionDefinitionService { - private readonly IAreaService _areaService; private readonly FlotillaDbContext _context; private readonly ICustomMissionService _customMissionService; private readonly IEchoService _echoService; private readonly ILogger _logger; - private readonly ISourceService _sourceService; private readonly IStidService _stidService; public MissionDefinitionService( @@ -61,16 +57,12 @@ public MissionDefinitionService( IEchoService echoService, IStidService stidService, ICustomMissionService customMissionService, - IAreaService areaService, - ISourceService sourceService, ILogger logger) { _context = context; _echoService = echoService; _stidService = stidService; _customMissionService = customMissionService; - _areaService = areaService; - _sourceService = sourceService; _logger = logger; } @@ -173,61 +165,13 @@ await _customMissionService.GetMissionTasksFromSourceId(source.SourceId), throw new MissionSourceTypeException($"Mission type {source.Type} is not accounted for") }; } - catch (FormatException e) + catch (FormatException) { _logger.LogError("Echo source ID was not formatted correctly"); throw new FormatException("Echo source ID was not formatted correctly"); } } - public async Task FindExistingOrCreateCustomMissionDefinition(CustomMissionQuery customMissionQuery, List missionTasks) - { - Area? area = null; - if (customMissionQuery.AreaName != null) { area = await _areaService.ReadByInstallationAndName(customMissionQuery.InstallationCode, customMissionQuery.AreaName); } - - var source = await _sourceService.CheckForExistingCustomSource(missionTasks); - - MissionDefinition? existingMissionDefinition = null; - if (source == null) - { - try - { - string sourceUrl = await _customMissionService.UploadSource(missionTasks); - source = new Source - { - SourceId = sourceUrl, Type = MissionSourceType.Custom - }; - } - catch (Exception e) - { - { - string errorMessage = $"Unable to upload source for mission {customMissionQuery.Name}"; - _logger.LogError(e, "{Message}", errorMessage); - throw new SourceException(errorMessage); - } - } - } - else - { - var missionDefinitions = await ReadBySourceId(source.SourceId); - if (missionDefinitions.Count > 0) { existingMissionDefinition = missionDefinitions.First(); } - } - - var customMissionDefinition = existingMissionDefinition ?? new MissionDefinition - { - Id = Guid.NewGuid().ToString(), - Source = source, - Name = customMissionQuery.Name, - InspectionFrequency = customMissionQuery.InspectionFrequency, - InstallationCode = customMissionQuery.InstallationCode, - Area = area - }; - - if (existingMissionDefinition == null) { await Create(customMissionDefinition); } - - return customMissionDefinition; - } - private IQueryable GetMissionDefinitionsWithSubModels() { return _context.MissionDefinitions