From 170f1954069841da7bcd4b3426d34a11df797077 Mon Sep 17 00:00:00 2001 From: aestene Date: Fri, 17 Jan 2025 12:41:11 +0100 Subject: [PATCH] Remove timeseries from postgreSQL database This functionality has not been used and shoudl thus be removed. In addition, it simplifies an upcoming implementation of postgreSQL database as part of tests. --- .../api/Controllers/TimeseriesController.cs | 111 ------ .../api/Database/Context/FlotillaDbContext.cs | 7 - .../Database/Models/RobotBatteryTimeseries.cs | 12 - .../Database/Models/RobotPoseTimeseries.cs | 45 --- .../Models/RobotPressureTimeseries.cs | 12 - backend/api/Database/Models/TimeseriesBase.cs | 17 - .../20241210093952_Reset-Structure.cs | 50 --- .../FlotillaDbContextModelSnapshot.cs | 114 +++--- backend/api/Program.cs | 4 - backend/api/Services/TimeseriesService.cs | 340 ------------------ .../api/Services/TimeseriesServiceSqlLite.cs | 106 ------ 11 files changed, 57 insertions(+), 761 deletions(-) delete mode 100644 backend/api/Controllers/TimeseriesController.cs delete mode 100644 backend/api/Database/Models/RobotBatteryTimeseries.cs delete mode 100644 backend/api/Database/Models/RobotPoseTimeseries.cs delete mode 100644 backend/api/Database/Models/RobotPressureTimeseries.cs delete mode 100644 backend/api/Database/Models/TimeseriesBase.cs delete mode 100644 backend/api/Services/TimeseriesService.cs delete mode 100644 backend/api/Services/TimeseriesServiceSqlLite.cs diff --git a/backend/api/Controllers/TimeseriesController.cs b/backend/api/Controllers/TimeseriesController.cs deleted file mode 100644 index c7ec9bb7d..000000000 --- a/backend/api/Controllers/TimeseriesController.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Api.Controllers.Models; -using Api.Database.Models; -using Api.Services; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace Api.Controllers -{ - [ApiController] - [Route("timeseries")] - [Authorize(Roles = Role.Any)] - public class TimeseriesController( - ILogger logger, - ITimeseriesService timeseriesService - ) : ControllerBase - { - /// - /// Get pressure timeseries - /// - /// - /// This query gets a paginated response of entries of the pressure timeseries - /// - [HttpGet] - [Route("pressure")] - [Authorize(Roles = Role.Any)] - [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetPressureTimeseries( - [FromQuery] TimeseriesQueryStringParameters queryStringParameters - ) - { - try - { - var robotPressureTimeseries = - await timeseriesService.ReadAll(queryStringParameters); - return Ok(robotPressureTimeseries); - } - catch (Exception e) - { - logger.LogError(e, "Error during GET of robot pressure timeseries from database"); - throw; - } - } - - /// - /// Get battery timeseries - /// - /// - /// This query gets a paginated response of entries of the battery timeseries - /// - [HttpGet] - [Route("battery")] - [Authorize(Roles = Role.Any)] - [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetBatteryTimeseries( - [FromQuery] TimeseriesQueryStringParameters queryStringParameters - ) - { - try - { - var robotBatteryTimeseries = - await timeseriesService.ReadAll(queryStringParameters); - return Ok(robotBatteryTimeseries); - } - catch (Exception e) - { - logger.LogError(e, "Error during GET of robot battery timeseries from database"); - throw; - } - } - - /// - /// Get pose timeseries - /// - /// - /// This query gets a paginated response of entries of the pose timeseries - /// - [HttpGet] - [Route("pose")] - [Authorize(Roles = Role.Any)] - [ProducesResponseType(typeof(IList), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public async Task>> GetPoseTimeseries( - [FromQuery] TimeseriesQueryStringParameters queryStringParameters - ) - { - try - { - var robotPoseTimeseries = await timeseriesService.ReadAll( - queryStringParameters - ); - return Ok(robotPoseTimeseries); - } - catch (Exception e) - { - logger.LogError(e, "Error during GET of robot pose timeseries from database"); - throw; - } - } - } -} diff --git a/backend/api/Database/Context/FlotillaDbContext.cs b/backend/api/Database/Context/FlotillaDbContext.cs index c938a0699..802b376c1 100644 --- a/backend/api/Database/Context/FlotillaDbContext.cs +++ b/backend/api/Database/Context/FlotillaDbContext.cs @@ -33,13 +33,6 @@ public FlotillaDbContext(DbContextOptions options) public DbSet UserInfos => Set(); public DbSet TagInspectionMetadata => Set(); - // Timeseries: - public DbSet RobotPressureTimeseries => - Set(); - public DbSet RobotBatteryTimeseries => - Set(); - public DbSet RobotPoseTimeseries => Set(); - protected override void OnModelCreating(ModelBuilder modelBuilder) { bool isSqlLite = Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite"; diff --git a/backend/api/Database/Models/RobotBatteryTimeseries.cs b/backend/api/Database/Models/RobotBatteryTimeseries.cs deleted file mode 100644 index dff849623..000000000 --- a/backend/api/Database/Models/RobotBatteryTimeseries.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.EntityFrameworkCore; - -namespace Api.Database.Models -{ - [Keyless] - public class RobotBatteryTimeseries : TimeseriesBase - { - [Required] - public float BatteryLevel { get; set; } - } -} diff --git a/backend/api/Database/Models/RobotPoseTimeseries.cs b/backend/api/Database/Models/RobotPoseTimeseries.cs deleted file mode 100644 index 0c659459a..000000000 --- a/backend/api/Database/Models/RobotPoseTimeseries.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.EntityFrameworkCore; - -namespace Api.Database.Models -{ - // Cannot use Pose as owned entity in keyless entity - // https://learn.microsoft.com/en-us/ef/core/modeling/keyless-entity-types?tabs=data-annotations - [Keyless] - public class RobotPoseTimeseries : TimeseriesBase - { - [Required] - public float PositionX { get; set; } - - [Required] - public float PositionY { get; set; } - - [Required] - public float PositionZ { get; set; } - - [Required] - public float OrientationX { get; set; } - - [Required] - public float OrientationY { get; set; } - - [Required] - public float OrientationZ { get; set; } - - [Required] - public float OrientationW { get; set; } - - public RobotPoseTimeseries(Pose pose) - { - PositionX = pose.Position.X; - PositionY = pose.Position.Y; - PositionZ = pose.Position.Z; - OrientationX = pose.Orientation.X; - OrientationY = pose.Orientation.Y; - OrientationZ = pose.Orientation.Z; - OrientationW = pose.Orientation.W; - } - - public RobotPoseTimeseries() { } - } -} diff --git a/backend/api/Database/Models/RobotPressureTimeseries.cs b/backend/api/Database/Models/RobotPressureTimeseries.cs deleted file mode 100644 index 034d05747..000000000 --- a/backend/api/Database/Models/RobotPressureTimeseries.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.EntityFrameworkCore; - -namespace Api.Database.Models -{ - [Keyless] - public class RobotPressureTimeseries : TimeseriesBase - { - [Required] - public float Pressure { get; set; } - } -} diff --git a/backend/api/Database/Models/TimeseriesBase.cs b/backend/api/Database/Models/TimeseriesBase.cs deleted file mode 100644 index 3adcdbbdb..000000000 --- a/backend/api/Database/Models/TimeseriesBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -#pragma warning disable CS8618 -namespace Api.Database.Models -{ - public class TimeseriesBase - { - [Required] - public DateTime Time { get; set; } - - [Required] - [ForeignKey(nameof(Robot))] - public string RobotId { get; set; } - - public string? MissionId { get; set; } - } -} diff --git a/backend/api/Migrations/20241210093952_Reset-Structure.cs b/backend/api/Migrations/20241210093952_Reset-Structure.cs index 7349f2699..6be95b297 100644 --- a/backend/api/Migrations/20241210093952_Reset-Structure.cs +++ b/backend/api/Migrations/20241210093952_Reset-Structure.cs @@ -11,11 +11,6 @@ public partial class ResetStructure : Migration /// protected override void Up(MigrationBuilder migrationBuilder) { - // MANUALLY ADDED: - // Adding timescale extension to the database - migrationBuilder.Sql("CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;"); - // - migrationBuilder.CreateTable( name: "DefaultLocalizationPoses", columns: table => new @@ -70,19 +65,6 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_Installations", x => x.Id); }); - migrationBuilder.CreateTable( - name: "RobotBatteryTimeseries", - columns: table => new - { - BatteryLevel = table.Column(type: "real", nullable: false), - Time = table.Column(type: "timestamp with time zone", nullable: false), - RobotId = table.Column(type: "text", nullable: false), - MissionId = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - }); - migrationBuilder.CreateTable( name: "RobotModels", columns: table => new @@ -100,38 +82,6 @@ protected override void Up(MigrationBuilder migrationBuilder) table.PrimaryKey("PK_RobotModels", x => x.Id); }); - migrationBuilder.CreateTable( - name: "RobotPoseTimeseries", - columns: table => new - { - PositionX = table.Column(type: "real", nullable: false), - PositionY = table.Column(type: "real", nullable: false), - PositionZ = table.Column(type: "real", nullable: false), - OrientationX = table.Column(type: "real", nullable: false), - OrientationY = table.Column(type: "real", nullable: false), - OrientationZ = table.Column(type: "real", nullable: false), - OrientationW = table.Column(type: "real", nullable: false), - Time = table.Column(type: "timestamp with time zone", nullable: false), - RobotId = table.Column(type: "text", nullable: false), - MissionId = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - }); - - migrationBuilder.CreateTable( - name: "RobotPressureTimeseries", - columns: table => new - { - Pressure = table.Column(type: "real", nullable: false), - Time = table.Column(type: "timestamp with time zone", nullable: false), - RobotId = table.Column(type: "text", nullable: false), - MissionId = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - }); - migrationBuilder.CreateTable( name: "Sources", columns: table => new diff --git a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs index 65ef9475a..5803c3c0d 100644 --- a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs +++ b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs @@ -43,7 +43,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("InstallationId"); - b.ToTable("AccessRoles"); + b.ToTable("AccessRoles", (string)null); }); modelBuilder.Entity("Api.Database.Models.Area", b => @@ -82,7 +82,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PlantId"); - b.ToTable("Areas"); + b.ToTable("Areas", (string)null); }); modelBuilder.Entity("Api.Database.Models.DefaultLocalizationPose", b => @@ -96,7 +96,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("DefaultLocalizationPoses"); + b.ToTable("DefaultLocalizationPoses", (string)null); }); modelBuilder.Entity("Api.Database.Models.Inspection", b => @@ -144,7 +144,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Inspections"); + b.ToTable("Inspections", (string)null); }); modelBuilder.Entity("Api.Database.Models.InspectionArea", b => @@ -177,7 +177,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PlantId"); - b.ToTable("InspectionAreas"); + b.ToTable("InspectionAreas", (string)null); }); modelBuilder.Entity("Api.Database.Models.InspectionFinding", b => @@ -204,7 +204,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("InspectionId"); - b.ToTable("InspectionFindings"); + b.ToTable("InspectionFindings", (string)null); }); modelBuilder.Entity("Api.Database.Models.Installation", b => @@ -228,7 +228,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("InstallationCode") .IsUnique(); - b.ToTable("Installations"); + b.ToTable("Installations", (string)null); }); modelBuilder.Entity("Api.Database.Models.MissionDefinition", b => @@ -274,7 +274,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SourceId"); - b.ToTable("MissionDefinitions"); + b.ToTable("MissionDefinitions", (string)null); }); modelBuilder.Entity("Api.Database.Models.MissionRun", b => @@ -348,7 +348,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("RobotId"); - b.ToTable("MissionRuns"); + b.ToTable("MissionRuns", (string)null); }); modelBuilder.Entity("Api.Database.Models.MissionTask", b => @@ -405,7 +405,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("MissionRunId"); - b.ToTable("MissionTasks"); + b.ToTable("MissionTasks", (string)null); }); modelBuilder.Entity("Api.Database.Models.Plant", b => @@ -435,7 +435,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PlantCode") .IsUnique(); - b.ToTable("Plants"); + b.ToTable("Plants", (string)null); }); modelBuilder.Entity("Api.Database.Models.Robot", b => @@ -518,7 +518,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ModelId"); - b.ToTable("Robots"); + b.ToTable("Robots", (string)null); }); modelBuilder.Entity("Api.Database.Models.RobotBatteryTimeseries", b => @@ -536,7 +536,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Time") .HasColumnType("timestamp with time zone"); - b.ToTable("RobotBatteryTimeseries"); + b.ToTable("RobotBatteryTimeseries", (string)null); }); modelBuilder.Entity("Api.Database.Models.RobotModel", b => @@ -569,7 +569,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("Type") .IsUnique(); - b.ToTable("RobotModels"); + b.ToTable("RobotModels", (string)null); }); modelBuilder.Entity("Api.Database.Models.RobotPoseTimeseries", b => @@ -605,7 +605,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Time") .HasColumnType("timestamp with time zone"); - b.ToTable("RobotPoseTimeseries"); + b.ToTable("RobotPoseTimeseries", (string)null); }); modelBuilder.Entity("Api.Database.Models.RobotPressureTimeseries", b => @@ -623,7 +623,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Time") .HasColumnType("timestamp with time zone"); - b.ToTable("RobotPressureTimeseries"); + b.ToTable("RobotPressureTimeseries", (string)null); }); modelBuilder.Entity("Api.Database.Models.Source", b => @@ -641,7 +641,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("Sources"); + b.ToTable("Sources", (string)null); }); modelBuilder.Entity("Api.Database.Models.UserInfo", b => @@ -656,7 +656,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.ToTable("UserInfos"); + b.ToTable("UserInfos", (string)null); }); modelBuilder.Entity("Api.Services.MissionLoaders.TagInspectionMetadata", b => @@ -666,7 +666,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("TagId"); - b.ToTable("TagInspectionMetadata"); + b.ToTable("TagInspectionMetadata", (string)null); }); modelBuilder.Entity("Api.Database.Models.AccessRole", b => @@ -702,7 +702,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Restrict) .IsRequired(); - b.OwnsOne("Api.Database.Models.MapMetadata", "MapMetadata", b1 => + b.OwnsOne("Api.Database.Models.Area.MapMetadata#Api.Database.Models.MapMetadata", "MapMetadata", b1 => { b1.Property("AreaId") .HasColumnType("text"); @@ -714,12 +714,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey("AreaId"); - b1.ToTable("Areas"); + b1.ToTable("Areas", (string)null); b1.WithOwner() .HasForeignKey("AreaId"); - b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + b1.OwnsOne("Api.Database.Models.Area.MapMetadata#Api.Database.Models.MapMetadata.Boundary#Api.Database.Models.Boundary", "Boundary", b2 => { b2.Property("MapMetadataAreaId") .HasColumnType("text"); @@ -744,13 +744,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("MapMetadataAreaId"); - b2.ToTable("Areas"); + b2.ToTable("Areas", (string)null); b2.WithOwner() .HasForeignKey("MapMetadataAreaId"); }); - b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + b1.OwnsOne("Api.Database.Models.Area.MapMetadata#Api.Database.Models.MapMetadata.TransformationMatrices#Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => { b2.Property("MapMetadataAreaId") .HasColumnType("text"); @@ -769,7 +769,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("MapMetadataAreaId"); - b2.ToTable("Areas"); + b2.ToTable("Areas", (string)null); b2.WithOwner() .HasForeignKey("MapMetadataAreaId"); @@ -796,19 +796,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Api.Database.Models.DefaultLocalizationPose", b => { - b.OwnsOne("Api.Database.Models.Pose", "Pose", b1 => + b.OwnsOne("Api.Database.Models.DefaultLocalizationPose.Pose#Api.Database.Models.Pose", "Pose", b1 => { b1.Property("DefaultLocalizationPoseId") .HasColumnType("text"); b1.HasKey("DefaultLocalizationPoseId"); - b1.ToTable("DefaultLocalizationPoses"); + b1.ToTable("DefaultLocalizationPoses", (string)null); b1.WithOwner() .HasForeignKey("DefaultLocalizationPoseId"); - b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + b1.OwnsOne("Api.Database.Models.DefaultLocalizationPose.Pose#Api.Database.Models.Pose.Orientation#Api.Database.Models.Orientation", "Orientation", b2 => { b2.Property("PoseDefaultLocalizationPoseId") .HasColumnType("text"); @@ -827,13 +827,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("PoseDefaultLocalizationPoseId"); - b2.ToTable("DefaultLocalizationPoses"); + b2.ToTable("DefaultLocalizationPoses", (string)null); b2.WithOwner() .HasForeignKey("PoseDefaultLocalizationPoseId"); }); - b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + b1.OwnsOne("Api.Database.Models.DefaultLocalizationPose.Pose#Api.Database.Models.Pose.Position#Api.Database.Models.Position", "Position", b2 => { b2.Property("PoseDefaultLocalizationPoseId") .HasColumnType("text"); @@ -849,7 +849,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("PoseDefaultLocalizationPoseId"); - b2.ToTable("DefaultLocalizationPoses"); + b2.ToTable("DefaultLocalizationPoses", (string)null); b2.WithOwner() .HasForeignKey("PoseDefaultLocalizationPoseId"); @@ -868,7 +868,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Api.Database.Models.Inspection", b => { - b.OwnsOne("Api.Database.Models.Position", "InspectionTarget", b1 => + b.OwnsOne("Api.Database.Models.Inspection.InspectionTarget#Api.Database.Models.Position", "InspectionTarget", b1 => { b1.Property("InspectionId") .HasColumnType("text"); @@ -884,7 +884,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey("InspectionId"); - b1.ToTable("Inspections"); + b1.ToTable("Inspections", (string)null); b1.WithOwner() .HasForeignKey("InspectionId"); @@ -943,7 +943,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.OwnsOne("Api.Database.Models.MapMetadata", "Map", b1 => + b.OwnsOne("Api.Database.Models.MissionDefinition.Map#Api.Database.Models.MapMetadata", "Map", b1 => { b1.Property("MissionDefinitionId") .HasColumnType("text"); @@ -955,12 +955,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey("MissionDefinitionId"); - b1.ToTable("MissionDefinitions"); + b1.ToTable("MissionDefinitions", (string)null); b1.WithOwner() .HasForeignKey("MissionDefinitionId"); - b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + b1.OwnsOne("Api.Database.Models.MissionDefinition.Map#Api.Database.Models.MapMetadata.Boundary#Api.Database.Models.Boundary", "Boundary", b2 => { b2.Property("MapMetadataMissionDefinitionId") .HasColumnType("text"); @@ -985,13 +985,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("MapMetadataMissionDefinitionId"); - b2.ToTable("MissionDefinitions"); + b2.ToTable("MissionDefinitions", (string)null); b2.WithOwner() .HasForeignKey("MapMetadataMissionDefinitionId"); }); - b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + b1.OwnsOne("Api.Database.Models.MissionDefinition.Map#Api.Database.Models.MapMetadata.TransformationMatrices#Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => { b2.Property("MapMetadataMissionDefinitionId") .HasColumnType("text"); @@ -1010,7 +1010,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("MapMetadataMissionDefinitionId"); - b2.ToTable("MissionDefinitions"); + b2.ToTable("MissionDefinitions", (string)null); b2.WithOwner() .HasForeignKey("MapMetadataMissionDefinitionId"); @@ -1060,7 +1060,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .WithMany("Tasks") .HasForeignKey("MissionRunId"); - b.OwnsOne("Api.Services.Models.IsarZoomDescription", "IsarZoomDescription", b1 => + b.OwnsOne("Api.Database.Models.MissionTask.IsarZoomDescription#Api.Services.Models.IsarZoomDescription", "IsarZoomDescription", b1 => { b1.Property("MissionTaskId") .HasColumnType("text"); @@ -1075,25 +1075,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey("MissionTaskId"); - b1.ToTable("MissionTasks"); + b1.ToTable("MissionTasks", (string)null); b1.WithOwner() .HasForeignKey("MissionTaskId"); }); - b.OwnsOne("Api.Database.Models.Pose", "RobotPose", b1 => + b.OwnsOne("Api.Database.Models.MissionTask.RobotPose#Api.Database.Models.Pose", "RobotPose", b1 => { b1.Property("MissionTaskId") .HasColumnType("text"); b1.HasKey("MissionTaskId"); - b1.ToTable("MissionTasks"); + b1.ToTable("MissionTasks", (string)null); b1.WithOwner() .HasForeignKey("MissionTaskId"); - b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + b1.OwnsOne("Api.Database.Models.MissionTask.RobotPose#Api.Database.Models.Pose.Orientation#Api.Database.Models.Orientation", "Orientation", b2 => { b2.Property("PoseMissionTaskId") .HasColumnType("text"); @@ -1112,13 +1112,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("PoseMissionTaskId"); - b2.ToTable("MissionTasks"); + b2.ToTable("MissionTasks", (string)null); b2.WithOwner() .HasForeignKey("PoseMissionTaskId"); }); - b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + b1.OwnsOne("Api.Database.Models.MissionTask.RobotPose#Api.Database.Models.Pose.Position#Api.Database.Models.Position", "Position", b2 => { b2.Property("PoseMissionTaskId") .HasColumnType("text"); @@ -1134,7 +1134,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("PoseMissionTaskId"); - b2.ToTable("MissionTasks"); + b2.ToTable("MissionTasks", (string)null); b2.WithOwner() .HasForeignKey("PoseMissionTaskId"); @@ -1184,7 +1184,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.OwnsMany("Api.Database.Models.DocumentInfo", "Documentation", b1 => + b.OwnsMany("Api.Database.Models.Robot.Documentation#Api.Database.Models.DocumentInfo", "Documentation", b1 => { b1.Property("Id") .ValueGeneratedOnAdd() @@ -1208,25 +1208,25 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasIndex("RobotId"); - b1.ToTable("DocumentInfo"); + b1.ToTable("DocumentInfo", (string)null); b1.WithOwner() .HasForeignKey("RobotId"); }); - b.OwnsOne("Api.Database.Models.Pose", "Pose", b1 => + b.OwnsOne("Api.Database.Models.Robot.Pose#Api.Database.Models.Pose", "Pose", b1 => { b1.Property("RobotId") .HasColumnType("text"); b1.HasKey("RobotId"); - b1.ToTable("Robots"); + b1.ToTable("Robots", (string)null); b1.WithOwner() .HasForeignKey("RobotId"); - b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + b1.OwnsOne("Api.Database.Models.Robot.Pose#Api.Database.Models.Pose.Orientation#Api.Database.Models.Orientation", "Orientation", b2 => { b2.Property("PoseRobotId") .HasColumnType("text"); @@ -1245,13 +1245,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("PoseRobotId"); - b2.ToTable("Robots"); + b2.ToTable("Robots", (string)null); b2.WithOwner() .HasForeignKey("PoseRobotId"); }); - b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + b1.OwnsOne("Api.Database.Models.Robot.Pose#Api.Database.Models.Pose.Position#Api.Database.Models.Position", "Position", b2 => { b2.Property("PoseRobotId") .HasColumnType("text"); @@ -1267,7 +1267,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b2.HasKey("PoseRobotId"); - b2.ToTable("Robots"); + b2.ToTable("Robots", (string)null); b2.WithOwner() .HasForeignKey("PoseRobotId"); @@ -1294,7 +1294,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Api.Services.MissionLoaders.TagInspectionMetadata", b => { - b.OwnsOne("Api.Services.Models.IsarZoomDescription", "ZoomDescription", b1 => + b.OwnsOne("Api.Services.MissionLoaders.TagInspectionMetadata.ZoomDescription#Api.Services.Models.IsarZoomDescription", "ZoomDescription", b1 => { b1.Property("TagInspectionMetadataTagId") .HasColumnType("text"); @@ -1309,7 +1309,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey("TagInspectionMetadataTagId"); - b1.ToTable("TagInspectionMetadata"); + b1.ToTable("TagInspectionMetadata", (string)null); b1.WithOwner() .HasForeignKey("TagInspectionMetadataTagId"); diff --git a/backend/api/Program.cs b/backend/api/Program.cs index ef0182c74..89e8300fd 100644 --- a/backend/api/Program.cs +++ b/backend/api/Program.cs @@ -98,10 +98,6 @@ .Configuration.GetSection("Database") .GetValue("UseInMemoryDatabase"); -if (useInMemoryDatabase) - builder.Services.AddScoped(); -else - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/backend/api/Services/TimeseriesService.cs b/backend/api/Services/TimeseriesService.cs deleted file mode 100644 index 3f58f589d..000000000 --- a/backend/api/Services/TimeseriesService.cs +++ /dev/null @@ -1,340 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Api.Controllers.Models; -using Api.Database.Context; -using Api.Database.Models; -using Api.Utilities; -using Microsoft.EntityFrameworkCore; -using Npgsql; - -namespace Api.Services -{ - public interface ITimeseriesService - { - public Task> ReadAll( - TimeseriesQueryStringParameters queryStringParameters - ) - where T : TimeseriesBase; - - public Task AddBatteryEntry(string currentMissionId, float batteryLevel, string robotId); - public Task AddPressureEntry(string currentMissionId, float pressureLevel, string robotId); - public Task AddPoseEntry(string currentMissionId, Pose robotPose, string robotId); - public Task Create(T newTimeseries) - where T : TimeseriesBase; - } - - [SuppressMessage( - "Globalization", - "CA1309:Use ordinal StringComparison", - Justification = "EF Core refrains from translating string comparison overloads to SQL" - )] - public class TimeseriesService : ITimeseriesService - { - private readonly FlotillaDbContext _context; - private readonly ILogger _logger; - private readonly NpgsqlDataSource _dataSource; - - public TimeseriesService(FlotillaDbContext context, ILogger logger) - { - string? connectionString = - context.Database.GetConnectionString() - ?? throw new NotSupportedException( - "Could not get connection string from EF core Database context - Cannot connect to Timeseries" - ); - var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString); - _dataSource = dataSourceBuilder.Build(); - _logger = logger; - _context = context; - } - - public async Task AddBatteryEntry( - string currentMissionId, - float batteryLevel, - string robotId - ) - { - try - { - await Create( - new RobotBatteryTimeseries - { - MissionId = currentMissionId, - BatteryLevel = batteryLevel, - RobotId = robotId, - Time = DateTime.UtcNow, - } - ); - } - catch (NpgsqlException e) - { - _logger.LogError( - e, - "An error occurred setting battery level while connecting to the timeseries database" - ); - } - } - - public async Task AddPressureEntry( - string currentMissionId, - float pressureLevel, - string robotId - ) - { - try - { - await Create( - new RobotPressureTimeseries - { - MissionId = currentMissionId, - Pressure = pressureLevel, - RobotId = robotId, - Time = DateTime.UtcNow, - } - ); - } - catch (NpgsqlException e) - { - _logger.LogError( - e, - "An error occurred setting pressure level while connecting to the timeseries database" - ); - } - } - - public async Task AddPoseEntry(string currentMissionId, Pose robotPose, string robotId) - { - try - { - await Create( - new RobotPoseTimeseries(robotPose) - { - MissionId = currentMissionId, - RobotId = robotId, - Time = DateTime.UtcNow, - } - ); - } - catch (NpgsqlException e) - { - _logger.LogError( - e, - "An error occurred setting pose while connecting to the timeseries database" - ); - } - } - - public async Task> ReadAll( - TimeseriesQueryStringParameters queryStringParameters - ) - where T : TimeseriesBase - { - var query = _context.Set().AsQueryable(); - var filter = ConstructFilter(queryStringParameters); - - query = query.Where(filter); - query = query.OrderByDescending(timeseries => timeseries.Time); - - return await PagedList.ToPagedListAsync( - query.OrderByDescending(timeseries => timeseries.Time), - queryStringParameters.PageNumber, - queryStringParameters.PageSize - ); - } - - // Cannot use Entity framework to insert keyless entities into the timeseries database - // https://gibinfrancis.medium.com/timescale-db-with-ef-core-94c948829608 - // https://github.com/npgsql/npgsql - // Unfortunately need to use npgsql framework with heavy statements for this. - public async Task Create(T newTimeseries) - where T : TimeseriesBase - { - await using var connection = await _dataSource.OpenConnectionAsync(); - - string tableName = - _context.Set().EntityType.GetTableName() - ?? throw new NotImplementedException( - $"Class '{nameof(T)}' is not mapped to a table" - ); - - await using var command = new NpgsqlCommand(); - command.Connection = connection; - if (newTimeseries.MissionId is not null) - { - command.CommandText = - $"INSERT INTO \"{tableName}\"" - + $"(\"{nameof(newTimeseries.Time)}\"," - + GetColumnNames(newTimeseries) - + $"\"{nameof(newTimeseries.RobotId)}\"," - + $"\"{nameof(newTimeseries.MissionId)}\") " - + "VALUES " - + $"(@{nameof(newTimeseries.Time)}, " - + GetValueNames(newTimeseries) - + $"@{nameof(newTimeseries.RobotId)}, " - + $"@{nameof(newTimeseries.MissionId)})"; - - command.Parameters.AddWithValue( - nameof(newTimeseries.MissionId), - newTimeseries.MissionId - ); - } - else - { - command.CommandText = - $"INSERT INTO \"{tableName}\"" - + $"(\"{nameof(newTimeseries.Time)}\"," - + GetColumnNames(newTimeseries) - + $"\"{nameof(newTimeseries.RobotId)}\") " - + "VALUES " - + $"(@{nameof(newTimeseries.Time)}, " - + GetValueNames(newTimeseries) - + $"@{nameof(newTimeseries.RobotId)})"; - } - - command.Parameters.AddWithValue(nameof(newTimeseries.RobotId), newTimeseries.RobotId); - command.Parameters.AddWithValue(nameof(newTimeseries.Time), newTimeseries.Time); - - AddParameterValues(command.Parameters, newTimeseries); - - await command.ExecuteNonQueryAsync(); - - await connection.CloseAsync(); - - return newTimeseries; - } - - private static Expression> ConstructFilter( - TimeseriesQueryStringParameters parameters - ) - where T : TimeseriesBase - { - Expression> robotIdFilter = parameters.RobotId is null - ? timeseries => true - : timeseries => timeseries.RobotId.Equals(parameters.RobotId); - - Expression> missionIdFilter = parameters.MissionId is null - ? timeseries => true - : timeseries => - timeseries.MissionId == null - || timeseries.MissionId.Equals(parameters.MissionId); - - var minStartTime = DateTimeUtilities.UnixTimeStampToDateTime(parameters.MinTime); - var maxStartTime = DateTimeUtilities.UnixTimeStampToDateTime(parameters.MaxTime); - Expression> timeFilter = timeseries => - DateTime.Compare(timeseries.Time, minStartTime) >= 0 - && DateTime.Compare(timeseries.Time, maxStartTime) <= 0; - - // The parameter of the filter expression - var timeseries = Expression.Parameter(typeof(T)); - - // Combining the body of the filters to create the combined filter, using invoke to force parameter substitution - Expression body = Expression.AndAlso( - Expression.Invoke(robotIdFilter, timeseries), - Expression.AndAlso( - Expression.Invoke(missionIdFilter, timeseries), - Expression.Invoke(timeFilter, timeseries) - ) - ); - - // Constructing the resulting lambda expression by combining parameter and body - return Expression.Lambda>(body, timeseries); - } - - private static void AddParameterValues(NpgsqlParameterCollection parameters, T entity) - { - if (entity is RobotPressureTimeseries robotPressureTimeseries) - { - parameters.AddWithValue( - nameof(robotPressureTimeseries.Pressure), - robotPressureTimeseries.Pressure - ); - } - else if (entity is RobotBatteryTimeseries robotBatteryTimeseries) - { - parameters.AddWithValue( - nameof(robotBatteryTimeseries.BatteryLevel), - robotBatteryTimeseries.BatteryLevel - ); - } - else if (entity is RobotPoseTimeseries robotPoseTimeseries) - { - // Position - parameters.AddWithValue( - nameof(robotPoseTimeseries.PositionX), - robotPoseTimeseries.PositionX - ); - parameters.AddWithValue( - nameof(robotPoseTimeseries.PositionY), - robotPoseTimeseries.PositionY - ); - parameters.AddWithValue( - nameof(robotPoseTimeseries.PositionZ), - robotPoseTimeseries.PositionZ - ); - - // Orientation - parameters.AddWithValue( - nameof(robotPoseTimeseries.OrientationX), - robotPoseTimeseries.OrientationX - ); - parameters.AddWithValue( - nameof(robotPoseTimeseries.OrientationY), - robotPoseTimeseries.OrientationY - ); - parameters.AddWithValue( - nameof(robotPoseTimeseries.OrientationZ), - robotPoseTimeseries.OrientationZ - ); - parameters.AddWithValue( - nameof(robotPoseTimeseries.OrientationW), - robotPoseTimeseries.OrientationW - ); - } - else - { - throw new NotImplementedException( - $"No parameter values defined for timeseries type '{nameof(T)}'" - ); - } - } - - private static string GetColumnNames(T entity) - where T : TimeseriesBase - { - return GetContentNames(entity, "\"", "\""); - } - - private static string GetValueNames(T entity) - where T : TimeseriesBase - { - return GetContentNames(entity, "@"); - } - - private static string GetContentNames( - T entity, - string namePrefix = "", - string namePostfix = "" - ) - where T : TimeseriesBase - { - if (entity is RobotPressureTimeseries robotPressureTimeseries) - { - return $"{namePrefix}{nameof(robotPressureTimeseries.Pressure)}{namePostfix},"; - } - - if (entity is RobotBatteryTimeseries robotBatteryTimeseries) - { - return $"{namePrefix}{nameof(robotBatteryTimeseries.BatteryLevel)}{namePostfix},"; - } - - if (entity is RobotPoseTimeseries robotPoseTimeseries) - { - return $"{namePrefix}{nameof(robotPoseTimeseries.PositionX)}{namePostfix},{namePrefix}{nameof(robotPoseTimeseries.PositionY)}{namePostfix},{namePrefix}{nameof(robotPoseTimeseries.PositionZ)}{namePostfix}," - + $"{namePrefix}{nameof(robotPoseTimeseries.OrientationX)}{namePostfix},{namePrefix}{nameof(robotPoseTimeseries.OrientationY)}{namePostfix},{namePrefix}{nameof(robotPoseTimeseries.OrientationZ)}{namePostfix},{namePrefix}{nameof(robotPoseTimeseries.OrientationW)}{namePostfix},"; - } - - throw new NotImplementedException( - $"No content names defined for timeseries type '{nameof(T)}'" - ); - } - } -} diff --git a/backend/api/Services/TimeseriesServiceSqlLite.cs b/backend/api/Services/TimeseriesServiceSqlLite.cs deleted file mode 100644 index 99224cae5..000000000 --- a/backend/api/Services/TimeseriesServiceSqlLite.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Api.Controllers.Models; -using Api.Database.Context; -using Api.Database.Models; -using Api.Utilities; - -namespace Api.Services -{ - /// - /// Uses only dotnet ef core, instead of the Npgsql package needed for the PostgreSql database - /// Cannot insert because it is a keyless entity - /// - [SuppressMessage( - "Globalization", - "CA1309:Use ordinal StringComparison", - Justification = "EF Core refrains from translating string comparison overloads to SQL" - )] - public class TimeseriesServiceSqlLite(FlotillaDbContext context) : ITimeseriesService - { - public async Task> ReadAll( - TimeseriesQueryStringParameters queryStringParameters - ) - where T : TimeseriesBase - { - var query = context.Set().AsQueryable(); - var filter = ConstructFilter(queryStringParameters); - - query = query.Where(filter); - query = query.OrderByDescending(timeseries => timeseries.Time); - - return await PagedList.ToPagedListAsync( - query.OrderByDescending(timeseries => timeseries.Time), - queryStringParameters.PageNumber, - queryStringParameters.PageSize - ); - } - - public async Task AddBatteryEntry( - string currentMissionId, - float batteryLevel, - string robotId - ) - { - await Task.CompletedTask; - } - - public async Task AddPressureEntry( - string currentMissionId, - float pressureLevel, - string robotId - ) - { - await Task.CompletedTask; - } - - public async Task AddPoseEntry(string currentMissionId, Pose robotPose, string robotId) - { - await Task.CompletedTask; - } - - public async Task Create(T newTimeseries) - where T : TimeseriesBase - { - await Task.CompletedTask; - return newTimeseries; - } - - private static Expression> ConstructFilter( - TimeseriesQueryStringParameters parameters - ) - where T : TimeseriesBase - { - Expression> robotIdFilter = parameters.RobotId is null - ? timeseries => true - : timeseries => timeseries.RobotId.Equals(parameters.RobotId); - - Expression> missionIdFilter = parameters.MissionId is null - ? timeseries => true - : timeseries => - timeseries.MissionId == null - || timeseries.MissionId.Equals(parameters.MissionId); - - var minStartTime = DateTimeUtilities.UnixTimeStampToDateTime(parameters.MinTime); - var maxStartTime = DateTimeUtilities.UnixTimeStampToDateTime(parameters.MaxTime); - Expression> timeFilter = timeseries => - DateTime.Compare(timeseries.Time, minStartTime) >= 0 - && DateTime.Compare(timeseries.Time, maxStartTime) <= 0; - - // The parameter of the filter expression - var timeseries = Expression.Parameter(typeof(T)); - - // Combining the body of the filters to create the combined filter, using invoke to force parameter substitution - Expression body = Expression.AndAlso( - Expression.Invoke(robotIdFilter, timeseries), - Expression.AndAlso( - Expression.Invoke(missionIdFilter, timeseries), - Expression.Invoke(timeFilter, timeseries) - ) - ); - - // Constructing the resulting lambda expression by combining parameter and body - return Expression.Lambda>(body, timeseries); - } - } -}