diff --git a/backend/api/Controllers/InspectionController.cs b/backend/api/Controllers/InspectionController.cs new file mode 100644 index 000000000..39ffe10a4 --- /dev/null +++ b/backend/api/Controllers/InspectionController.cs @@ -0,0 +1,53 @@ +using Api.Controllers.Models; +using Api.Services; +using Api.Services.MissionLoaders; +using Api.Services.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Api.Controllers +{ + [ApiController] + [Route("inspection")] + public class InspectionController( + ILogger logger, + IEchoService echoService + ) : ControllerBase + { + /// + /// Updates the Flotilla metadata for an inspection tag + /// + /// + /// This query updates the Flotilla metadata for an inpection tag + /// + [HttpPost("{tagId}/tag-zoom")] + [Authorize(Roles = Role.Admin)] + [ProducesResponseType(typeof(TagInspectionMetadata), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public async Task> Create([FromRoute] string tagId, [FromBody] IsarZoomDescription zoom) + { + logger.LogInformation($"Updating zoom value for tag with ID {tagId}"); + + var newMetadata = new TagInspectionMetadata + { + TagId = tagId, + ZoomDescription = zoom + }; + + try + { + var metadata = await echoService.CreateOrUpdateTagInspectionMetadata(newMetadata); + + return metadata; + } + catch (Exception e) + { + logger.LogError(e, "Error while creating or updating inspection tag metadata"); + throw; + } + } + } +} diff --git a/backend/api/Controllers/Models/CustomMissionQuery.cs b/backend/api/Controllers/Models/CustomMissionQuery.cs index 9421e9ef7..8d027cdcd 100644 --- a/backend/api/Controllers/Models/CustomMissionQuery.cs +++ b/backend/api/Controllers/Models/CustomMissionQuery.cs @@ -1,4 +1,5 @@ using Api.Database.Models; +using Api.Services.Models; namespace Api.Controllers.Models { public struct CustomInspectionQuery @@ -22,6 +23,8 @@ public struct CustomTaskQuery public Pose RobotPose { get; set; } + public IsarZoomDescription? IsarZoomDescription { get; set; } + public CustomInspectionQuery? Inspection { get; set; } } @@ -44,5 +47,7 @@ public struct CustomMissionQuery public string? Comment { get; set; } public List Tasks { get; set; } + + public IsarZoomDescription? IsarZoomDescription { get; set; } } } diff --git a/backend/api/Database/Context/FlotillaDbContext.cs b/backend/api/Database/Context/FlotillaDbContext.cs index 1a03536f9..8dc00048f 100644 --- a/backend/api/Database/Context/FlotillaDbContext.cs +++ b/backend/api/Database/Context/FlotillaDbContext.cs @@ -1,4 +1,5 @@ using Api.Database.Models; +using Api.Services.MissionLoaders; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -26,6 +27,7 @@ public FlotillaDbContext(DbContextOptions options) : base(options) public DbSet DefaultLocalizationPoses => Set(); public DbSet AccessRoles => Set(); public DbSet UserInfos => Set(); + public DbSet TagInspectionMetadata => Set(); // Timeseries: public DbSet RobotPressureTimeseries => Set(); diff --git a/backend/api/Database/Models/MissionTask.cs b/backend/api/Database/Models/MissionTask.cs index d78fcdcd7..92f32c934 100644 --- a/backend/api/Database/Models/MissionTask.cs +++ b/backend/api/Database/Models/MissionTask.cs @@ -25,6 +25,7 @@ public MissionTask( Uri? tagLink, string? tagId, int? poseId, + IsarZoomDescription? zoomDescription = null, TaskStatus status = TaskStatus.NotStarted, MissionTaskType type = MissionTaskType.Inspection) { @@ -35,6 +36,7 @@ public MissionTask( TaskOrder = taskOrder; Status = status; Type = type; + IsarZoomDescription = zoomDescription; if (inspection != null) Inspection = new Inspection(inspection); } @@ -45,6 +47,7 @@ public MissionTask(CustomTaskQuery taskQuery) RobotPose = taskQuery.RobotPose; TaskOrder = taskQuery.TaskOrder; Status = TaskStatus.NotStarted; + IsarZoomDescription = taskQuery.IsarZoomDescription; if (taskQuery.Inspection is not null) { Inspection = new Inspection((CustomInspectionQuery)taskQuery.Inspection); @@ -98,6 +101,7 @@ public MissionTask(MissionTask copy, TaskStatus? status = null) RobotPose = new Pose(copy.RobotPose); PoseId = copy.PoseId; Status = status ?? copy.Status; + IsarZoomDescription = copy.IsarZoomDescription; if (copy.Inspection is not null) { Inspection = new Inspection(copy.Inspection, InspectionStatus.NotStarted); @@ -156,6 +160,8 @@ or TaskStatus.Failed public DateTime? EndTime { get; private set; } + public IsarZoomDescription? IsarZoomDescription { get; set; } + public Inspection? Inspection { get; set; } public void UpdateWithIsarInfo(IsarTask isarTask) diff --git a/backend/api/Database/Models/TagInspectionMetadata.cs b/backend/api/Database/Models/TagInspectionMetadata.cs new file mode 100644 index 000000000..8b651c0b6 --- /dev/null +++ b/backend/api/Database/Models/TagInspectionMetadata.cs @@ -0,0 +1,14 @@ +#nullable disable +using System.ComponentModel.DataAnnotations; +using Api.Services.Models; + +namespace Api.Services.MissionLoaders +{ + public class TagInspectionMetadata + { + [Key] + public string TagId { get; set; } + + public IsarZoomDescription ZoomDescription { get; set; } + } +} diff --git a/backend/api/Services/EchoService.cs b/backend/api/Services/EchoService.cs index 65ddb3087..5ff065f55 100644 --- a/backend/api/Services/EchoService.cs +++ b/backend/api/Services/EchoService.cs @@ -1,9 +1,12 @@ using System.Globalization; using System.Text.Json; using Api.Controllers.Models; +using Api.Database.Context; using Api.Database.Models; using Api.Services.MissionLoaders; +using Api.Services.Models; using Api.Utilities; +using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Abstractions; namespace Api.Services { @@ -13,10 +16,11 @@ public interface IEchoService public Task GetMissionById(string sourceMissionId); public Task> GetTasksForMission(string missionSourceId); public Task> GetPlantInfos(); + public Task CreateOrUpdateTagInspectionMetadata(TagInspectionMetadata metadata); } public class EchoService( - ILogger logger, IDownstreamApi echoApi, ISourceService sourceService, IStidService stidService) : IEchoService + ILogger logger, IDownstreamApi echoApi, ISourceService sourceService, IStidService stidService, FlotillaDbContext context) : IEchoService { public const string ServiceName = "EchoApi"; @@ -93,7 +97,7 @@ private async Task GetEchoMission(string echoMissionId) public async Task> GetTasksForMission(string missionSourceId) { var echoMission = await GetEchoMission(missionSourceId); - var missionTasks = echoMission.Tags.SelectMany(t => MissionTasksFromEchoTag(t)).ToList(); + var missionTasks = echoMission.Tags.Select(t => MissionTasksFromEchoTag(t)).SelectMany(task => task.Result).ToList(); return missionTasks; } @@ -269,7 +273,7 @@ List echoPlantInfoResponse return echoPlantInfos; } - public IList MissionTasksFromEchoTag(EchoTag echoTag) + public async Task> MissionTasksFromEchoTag(EchoTag echoTag) { var inspections = echoTag.Inspections .Select(inspection => new Inspection( @@ -292,6 +296,7 @@ public IList MissionTasksFromEchoTag(EchoTag echoTag) robotPose: echoTag.Pose, poseId: echoTag.PoseId, taskOrder: echoTag.PlanOrder, + zoomDescription: await FindInspectionZoom(echoTag), status: Database.Models.TaskStatus.NotStarted, type: MissionTaskType.Inspection )); @@ -299,5 +304,27 @@ public IList MissionTasksFromEchoTag(EchoTag echoTag) return missionTasks; } + + public async Task CreateOrUpdateTagInspectionMetadata(TagInspectionMetadata metadata) + { + var existingMetadata = await context.TagInspectionMetadata.Where(e => e.TagId == metadata.TagId).FirstOrDefaultAsync(); + if (existingMetadata == null) + { + await context.TagInspectionMetadata.AddAsync(metadata); + } + else + { + existingMetadata.ZoomDescription = metadata.ZoomDescription; + context.TagInspectionMetadata.Update(existingMetadata); + } + + await context.SaveChangesAsync(); + return metadata; + } + + private async Task FindInspectionZoom(EchoTag echoTag) + { + return (await context.TagInspectionMetadata.Where((e) => e.TagId == echoTag.TagId).FirstOrDefaultAsync())?.ZoomDescription; + } } } diff --git a/backend/api/Services/Models/IsarMissionDefinition.cs b/backend/api/Services/Models/IsarMissionDefinition.cs index 1573479f8..7d4347488 100644 --- a/backend/api/Services/Models/IsarMissionDefinition.cs +++ b/backend/api/Services/Models/IsarMissionDefinition.cs @@ -1,6 +1,8 @@ -using System.Globalization; +using System.ComponentModel.DataAnnotations; +using System.Globalization; using System.Text.Json.Serialization; using Api.Database.Models; +using Microsoft.EntityFrameworkCore; namespace Api.Services.Models { /// @@ -61,12 +63,17 @@ public struct IsarTaskDefinition [JsonPropertyName("inspection")] public IsarInspectionDefinition? Inspection { get; set; } + [JsonPropertyName("zoom")] + public IsarZoomDescription? Zoom { get; set; } + public IsarTaskDefinition(MissionTask missionTask, MissionRun missionRun) { Id = missionTask.IsarTaskId; Type = MissionTask.ConvertMissionTaskTypeToIsarTaskType(missionTask.Type); Pose = new IsarPose(missionTask.RobotPose); Tag = missionTask.TagId; + Zoom = missionTask.IsarZoomDescription; + if (missionTask.Inspection != null) Inspection = new IsarInspectionDefinition(missionTask.Inspection, missionRun); } } @@ -162,4 +169,16 @@ public readonly struct IsarPose(Pose pose) [JsonPropertyName("frame_name")] public string FrameName { get; } = "asset"; } + + [Owned] + public class IsarZoomDescription(double objectWidth, double objectHeight) + { + [Required] + [JsonPropertyName("objectWidth")] + public double ObjectWidth { get; set; } = objectWidth; + + [Required] + [JsonPropertyName("objectHeight")] + public double ObjectHeight { get; set; } = objectHeight; + } }