From d431852c8a56e9f5a2983f0fb487c4a1fb38c8b7 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 - ...1_RemoveTimeseriesFromPostgres.Designer.cs | 1264 +++++++++++++++++ ...0117113531_RemoveTimeseriesFromPostgres.cs | 22 + .../FlotillaDbContextModelSnapshot.cs | 72 - backend/api/Program.cs | 4 - backend/api/Services/TimeseriesService.cs | 340 ----- .../api/Services/TimeseriesServiceSqlLite.cs | 106 -- 13 files changed, 1286 insertions(+), 776 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 create mode 100644 backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.Designer.cs create mode 100644 backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.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/20250117113531_RemoveTimeseriesFromPostgres.Designer.cs b/backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.Designer.cs new file mode 100644 index 000000000..41c7c7b71 --- /dev/null +++ b/backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.Designer.cs @@ -0,0 +1,1264 @@ +// +using System; +using Api.Database.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Api.Migrations +{ + [DbContext(typeof(FlotillaDbContext))] + [Migration("20250117113531_RemoveTimeseriesFromPostgres")] + partial class RemoveTimeseriesFromPostgres + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Api.Database.Models.AccessRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AccessLevel") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstallationId") + .HasColumnType("text"); + + b.Property("RoleName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InstallationId"); + + b.ToTable("AccessRoles"); + }); + + modelBuilder.Entity("Api.Database.Models.Area", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DefaultLocalizationPoseId") + .HasColumnType("text"); + + b.Property("InspectionAreaId") + .IsRequired() + .HasColumnType("text"); + + b.Property("InstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PlantId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DefaultLocalizationPoseId"); + + b.HasIndex("InspectionAreaId"); + + b.HasIndex("InstallationId"); + + b.HasIndex("PlantId"); + + b.ToTable("Areas"); + }); + + modelBuilder.Entity("Api.Database.Models.DefaultLocalizationPose", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DockingEnabled") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DefaultLocalizationPoses"); + }); + + modelBuilder.Entity("Api.Database.Models.Inspection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AnalysisType") + .HasColumnType("text"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("InspectionTargetName") + .HasColumnType("text"); + + b.Property("InspectionType") + .IsRequired() + .HasColumnType("text"); + + b.Property("InspectionUrl") + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("IsarInspectionId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsarTaskId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("VideoDuration") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.ToTable("Inspections"); + }); + + modelBuilder.Entity("Api.Database.Models.InspectionArea", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("DefaultLocalizationPoseId") + .HasColumnType("text"); + + b.Property("InstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PlantId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DefaultLocalizationPoseId"); + + b.HasIndex("InstallationId"); + + b.HasIndex("PlantId"); + + b.ToTable("InspectionAreas"); + }); + + modelBuilder.Entity("Api.Database.Models.InspectionFinding", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Finding") + .IsRequired() + .HasColumnType("text"); + + b.Property("InspectionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("InspectionId") + .HasColumnType("text"); + + b.Property("IsarTaskId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionId"); + + b.ToTable("InspectionFindings"); + }); + + modelBuilder.Entity("Api.Database.Models.Installation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("InstallationCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("InstallationCode") + .IsUnique(); + + b.ToTable("Installations"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("InspectionAreaId") + .HasColumnType("text"); + + b.Property("InspectionFrequency") + .HasColumnType("bigint"); + + b.Property("InstallationCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeprecated") + .HasColumnType("boolean"); + + b.Property("LastSuccessfulRunId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionAreaId"); + + b.HasIndex("LastSuccessfulRunId"); + + b.HasIndex("SourceId"); + + b.ToTable("MissionDefinitions"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Comment") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Description") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("DesiredStartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EstimatedDuration") + .HasColumnType("bigint"); + + b.Property("InspectionAreaId") + .HasColumnType("text"); + + b.Property("InstallationCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsDeprecated") + .HasColumnType("boolean"); + + b.Property("IsarMissionId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MissionId") + .HasColumnType("text"); + + b.Property("MissionRunType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StatusReason") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.HasKey("Id"); + + b.HasIndex("InspectionAreaId"); + + b.HasIndex("RobotId"); + + b.ToTable("MissionRuns"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone"); + + b.Property("InspectionId") + .HasColumnType("text"); + + b.Property("IsarTaskId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MissionRunId") + .HasColumnType("text"); + + b.Property("PoseId") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TagId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TagLink") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("TaskOrder") + .HasColumnType("integer"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InspectionId"); + + b.HasIndex("MissionRunId"); + + b.ToTable("MissionTasks"); + }); + + modelBuilder.Entity("Api.Database.Models.Plant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("InstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PlantCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.HasKey("Id"); + + b.HasIndex("InstallationId"); + + b.HasIndex("PlantCode") + .IsUnique(); + + b.ToTable("Plants"); + }); + + modelBuilder.Entity("Api.Database.Models.Robot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("BatteryLevel") + .HasColumnType("real"); + + b.Property("BatteryState") + .HasColumnType("text"); + + b.Property("CurrentInspectionAreaId") + .HasColumnType("text"); + + b.Property("CurrentInstallationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentMissionId") + .HasColumnType("text"); + + b.Property("Deprecated") + .HasColumnType("boolean"); + + b.Property("FlotillaStatus") + .IsRequired() + .HasColumnType("text"); + + b.Property("Host") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsarConnected") + .HasColumnType("boolean"); + + b.Property("IsarId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MissionQueueFrozen") + .HasColumnType("boolean"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("PressureLevel") + .HasColumnType("real"); + + b.Property("RobotCapabilities") + .HasColumnType("text"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CurrentInspectionAreaId"); + + b.HasIndex("CurrentInstallationId"); + + b.HasIndex("ModelId"); + + b.ToTable("Robots"); + }); + + modelBuilder.Entity("Api.Database.Models.RobotModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("AverageDurationPerTag") + .HasColumnType("real"); + + b.Property("BatteryMissionStartThreshold") + .HasColumnType("real"); + + b.Property("BatteryWarningThreshold") + .HasColumnType("real"); + + b.Property("LowerPressureWarningThreshold") + .HasColumnType("real"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpperPressureWarningThreshold") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.HasIndex("Type") + .IsUnique(); + + b.ToTable("RobotModels"); + }); + + modelBuilder.Entity("Api.Database.Models.Source", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("CustomMissionTasks") + .HasColumnType("text"); + + b.Property("SourceId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Sources"); + }); + + modelBuilder.Entity("Api.Database.Models.UserInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("Oid") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("UserInfos"); + }); + + modelBuilder.Entity("Api.Services.MissionLoaders.TagInspectionMetadata", b => + { + b.Property("TagId") + .HasColumnType("text"); + + b.HasKey("TagId"); + + b.ToTable("TagInspectionMetadata"); + }); + + modelBuilder.Entity("Api.Database.Models.AccessRole", b => + { + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId"); + + b.Navigation("Installation"); + }); + + modelBuilder.Entity("Api.Database.Models.Area", b => + { + b.HasOne("Api.Database.Models.DefaultLocalizationPose", "DefaultLocalizationPose") + .WithMany() + .HasForeignKey("DefaultLocalizationPoseId"); + + b.HasOne("Api.Database.Models.InspectionArea", "InspectionArea") + .WithMany() + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Api.Database.Models.Plant", "Plant") + .WithMany() + .HasForeignKey("PlantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.OwnsOne("Api.Database.Models.MapMetadata", "MapMetadata", b1 => + { + b1.Property("AreaId") + .HasColumnType("text"); + + b1.Property("MapName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("AreaId"); + + b1.ToTable("Areas"); + + b1.WithOwner() + .HasForeignKey("AreaId"); + + b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + { + b2.Property("MapMetadataAreaId") + .HasColumnType("text"); + + b2.Property("X1") + .HasColumnType("double precision"); + + b2.Property("X2") + .HasColumnType("double precision"); + + b2.Property("Y1") + .HasColumnType("double precision"); + + b2.Property("Y2") + .HasColumnType("double precision"); + + b2.Property("Z1") + .HasColumnType("double precision"); + + b2.Property("Z2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataAreaId"); + + b2.ToTable("Areas"); + + b2.WithOwner() + .HasForeignKey("MapMetadataAreaId"); + }); + + b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + { + b2.Property("MapMetadataAreaId") + .HasColumnType("text"); + + b2.Property("C1") + .HasColumnType("double precision"); + + b2.Property("C2") + .HasColumnType("double precision"); + + b2.Property("D1") + .HasColumnType("double precision"); + + b2.Property("D2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataAreaId"); + + b2.ToTable("Areas"); + + b2.WithOwner() + .HasForeignKey("MapMetadataAreaId"); + }); + + b1.Navigation("Boundary") + .IsRequired(); + + b1.Navigation("TransformationMatrices") + .IsRequired(); + }); + + b.Navigation("DefaultLocalizationPose"); + + b.Navigation("InspectionArea"); + + b.Navigation("Installation"); + + b.Navigation("MapMetadata") + .IsRequired(); + + b.Navigation("Plant"); + }); + + modelBuilder.Entity("Api.Database.Models.DefaultLocalizationPose", b => + { + b.OwnsOne("Api.Database.Models.Pose", "Pose", b1 => + { + b1.Property("DefaultLocalizationPoseId") + .HasColumnType("text"); + + b1.HasKey("DefaultLocalizationPoseId"); + + b1.ToTable("DefaultLocalizationPoses"); + + b1.WithOwner() + .HasForeignKey("DefaultLocalizationPoseId"); + + b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + { + b2.Property("PoseDefaultLocalizationPoseId") + .HasColumnType("text"); + + b2.Property("W") + .HasColumnType("real"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseDefaultLocalizationPoseId"); + + b2.ToTable("DefaultLocalizationPoses"); + + b2.WithOwner() + .HasForeignKey("PoseDefaultLocalizationPoseId"); + }); + + b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + { + b2.Property("PoseDefaultLocalizationPoseId") + .HasColumnType("text"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseDefaultLocalizationPoseId"); + + b2.ToTable("DefaultLocalizationPoses"); + + b2.WithOwner() + .HasForeignKey("PoseDefaultLocalizationPoseId"); + }); + + b1.Navigation("Orientation") + .IsRequired(); + + b1.Navigation("Position") + .IsRequired(); + }); + + b.Navigation("Pose") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Database.Models.Inspection", b => + { + b.OwnsOne("Api.Database.Models.Position", "InspectionTarget", b1 => + { + b1.Property("InspectionId") + .HasColumnType("text"); + + b1.Property("X") + .HasColumnType("real"); + + b1.Property("Y") + .HasColumnType("real"); + + b1.Property("Z") + .HasColumnType("real"); + + b1.HasKey("InspectionId"); + + b1.ToTable("Inspections"); + + b1.WithOwner() + .HasForeignKey("InspectionId"); + }); + + b.Navigation("InspectionTarget") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Database.Models.InspectionArea", b => + { + b.HasOne("Api.Database.Models.DefaultLocalizationPose", "DefaultLocalizationPose") + .WithMany() + .HasForeignKey("DefaultLocalizationPoseId"); + + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Api.Database.Models.Plant", "Plant") + .WithMany() + .HasForeignKey("PlantId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DefaultLocalizationPose"); + + b.Navigation("Installation"); + + b.Navigation("Plant"); + }); + + modelBuilder.Entity("Api.Database.Models.InspectionFinding", b => + { + b.HasOne("Api.Database.Models.Inspection", null) + .WithMany("InspectionFindings") + .HasForeignKey("InspectionId"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionDefinition", b => + { + b.HasOne("Api.Database.Models.InspectionArea", "InspectionArea") + .WithMany() + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Api.Database.Models.MissionRun", "LastSuccessfulRun") + .WithMany() + .HasForeignKey("LastSuccessfulRunId"); + + b.HasOne("Api.Database.Models.Source", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsOne("Api.Database.Models.MapMetadata", "Map", b1 => + { + b1.Property("MissionDefinitionId") + .HasColumnType("text"); + + b1.Property("MapName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("MissionDefinitionId"); + + b1.ToTable("MissionDefinitions"); + + b1.WithOwner() + .HasForeignKey("MissionDefinitionId"); + + b1.OwnsOne("Api.Database.Models.Boundary", "Boundary", b2 => + { + b2.Property("MapMetadataMissionDefinitionId") + .HasColumnType("text"); + + b2.Property("X1") + .HasColumnType("double precision"); + + b2.Property("X2") + .HasColumnType("double precision"); + + b2.Property("Y1") + .HasColumnType("double precision"); + + b2.Property("Y2") + .HasColumnType("double precision"); + + b2.Property("Z1") + .HasColumnType("double precision"); + + b2.Property("Z2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataMissionDefinitionId"); + + b2.ToTable("MissionDefinitions"); + + b2.WithOwner() + .HasForeignKey("MapMetadataMissionDefinitionId"); + }); + + b1.OwnsOne("Api.Database.Models.TransformationMatrices", "TransformationMatrices", b2 => + { + b2.Property("MapMetadataMissionDefinitionId") + .HasColumnType("text"); + + b2.Property("C1") + .HasColumnType("double precision"); + + b2.Property("C2") + .HasColumnType("double precision"); + + b2.Property("D1") + .HasColumnType("double precision"); + + b2.Property("D2") + .HasColumnType("double precision"); + + b2.HasKey("MapMetadataMissionDefinitionId"); + + b2.ToTable("MissionDefinitions"); + + b2.WithOwner() + .HasForeignKey("MapMetadataMissionDefinitionId"); + }); + + b1.Navigation("Boundary") + .IsRequired(); + + b1.Navigation("TransformationMatrices") + .IsRequired(); + }); + + b.Navigation("InspectionArea"); + + b.Navigation("LastSuccessfulRun"); + + b.Navigation("Map"); + + b.Navigation("Source"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.HasOne("Api.Database.Models.InspectionArea", "InspectionArea") + .WithMany() + .HasForeignKey("InspectionAreaId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Api.Database.Models.Robot", "Robot") + .WithMany() + .HasForeignKey("RobotId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InspectionArea"); + + b.Navigation("Robot"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionTask", b => + { + b.HasOne("Api.Database.Models.Inspection", "Inspection") + .WithMany() + .HasForeignKey("InspectionId"); + + b.HasOne("Api.Database.Models.MissionRun", null) + .WithMany("Tasks") + .HasForeignKey("MissionRunId"); + + b.OwnsOne("Api.Services.Models.IsarZoomDescription", "IsarZoomDescription", b1 => + { + b1.Property("MissionTaskId") + .HasColumnType("text"); + + b1.Property("ObjectHeight") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectHeight"); + + b1.Property("ObjectWidth") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectWidth"); + + b1.HasKey("MissionTaskId"); + + b1.ToTable("MissionTasks"); + + b1.WithOwner() + .HasForeignKey("MissionTaskId"); + }); + + b.OwnsOne("Api.Database.Models.Pose", "RobotPose", b1 => + { + b1.Property("MissionTaskId") + .HasColumnType("text"); + + b1.HasKey("MissionTaskId"); + + b1.ToTable("MissionTasks"); + + b1.WithOwner() + .HasForeignKey("MissionTaskId"); + + b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + { + b2.Property("PoseMissionTaskId") + .HasColumnType("text"); + + b2.Property("W") + .HasColumnType("real"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseMissionTaskId"); + + b2.ToTable("MissionTasks"); + + b2.WithOwner() + .HasForeignKey("PoseMissionTaskId"); + }); + + b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + { + b2.Property("PoseMissionTaskId") + .HasColumnType("text"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseMissionTaskId"); + + b2.ToTable("MissionTasks"); + + b2.WithOwner() + .HasForeignKey("PoseMissionTaskId"); + }); + + b1.Navigation("Orientation") + .IsRequired(); + + b1.Navigation("Position") + .IsRequired(); + }); + + b.Navigation("Inspection"); + + b.Navigation("IsarZoomDescription"); + + b.Navigation("RobotPose") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Database.Models.Plant", b => + { + b.HasOne("Api.Database.Models.Installation", "Installation") + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Installation"); + }); + + modelBuilder.Entity("Api.Database.Models.Robot", b => + { + b.HasOne("Api.Database.Models.InspectionArea", "CurrentInspectionArea") + .WithMany() + .HasForeignKey("CurrentInspectionAreaId"); + + b.HasOne("Api.Database.Models.Installation", "CurrentInstallation") + .WithMany() + .HasForeignKey("CurrentInstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Api.Database.Models.RobotModel", "Model") + .WithMany() + .HasForeignKey("ModelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Api.Database.Models.DocumentInfo", "Documentation", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b1.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.Property("RobotId") + .IsRequired() + .HasColumnType("text"); + + b1.Property("Url") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b1.HasKey("Id"); + + b1.HasIndex("RobotId"); + + b1.ToTable("DocumentInfo"); + + b1.WithOwner() + .HasForeignKey("RobotId"); + }); + + b.OwnsOne("Api.Database.Models.Pose", "Pose", b1 => + { + b1.Property("RobotId") + .HasColumnType("text"); + + b1.HasKey("RobotId"); + + b1.ToTable("Robots"); + + b1.WithOwner() + .HasForeignKey("RobotId"); + + b1.OwnsOne("Api.Database.Models.Orientation", "Orientation", b2 => + { + b2.Property("PoseRobotId") + .HasColumnType("text"); + + b2.Property("W") + .HasColumnType("real"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseRobotId"); + + b2.ToTable("Robots"); + + b2.WithOwner() + .HasForeignKey("PoseRobotId"); + }); + + b1.OwnsOne("Api.Database.Models.Position", "Position", b2 => + { + b2.Property("PoseRobotId") + .HasColumnType("text"); + + b2.Property("X") + .HasColumnType("real"); + + b2.Property("Y") + .HasColumnType("real"); + + b2.Property("Z") + .HasColumnType("real"); + + b2.HasKey("PoseRobotId"); + + b2.ToTable("Robots"); + + b2.WithOwner() + .HasForeignKey("PoseRobotId"); + }); + + b1.Navigation("Orientation") + .IsRequired(); + + b1.Navigation("Position") + .IsRequired(); + }); + + b.Navigation("CurrentInspectionArea"); + + b.Navigation("CurrentInstallation"); + + b.Navigation("Documentation"); + + b.Navigation("Model"); + + b.Navigation("Pose") + .IsRequired(); + }); + + modelBuilder.Entity("Api.Services.MissionLoaders.TagInspectionMetadata", b => + { + b.OwnsOne("Api.Services.Models.IsarZoomDescription", "ZoomDescription", b1 => + { + b1.Property("TagInspectionMetadataTagId") + .HasColumnType("text"); + + b1.Property("ObjectHeight") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectHeight"); + + b1.Property("ObjectWidth") + .HasColumnType("double precision") + .HasAnnotation("Relational:JsonPropertyName", "objectWidth"); + + b1.HasKey("TagInspectionMetadataTagId"); + + b1.ToTable("TagInspectionMetadata"); + + b1.WithOwner() + .HasForeignKey("TagInspectionMetadataTagId"); + }); + + b.Navigation("ZoomDescription"); + }); + + modelBuilder.Entity("Api.Database.Models.Inspection", b => + { + b.Navigation("InspectionFindings"); + }); + + modelBuilder.Entity("Api.Database.Models.MissionRun", b => + { + b.Navigation("Tasks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.cs b/backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.cs new file mode 100644 index 000000000..74a7b02c6 --- /dev/null +++ b/backend/api/Migrations/20250117113531_RemoveTimeseriesFromPostgres.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Api.Migrations +{ + /// + public partial class RemoveTimeseriesFromPostgres : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs index 65ef9475a..4b19e493b 100644 --- a/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs +++ b/backend/api/Migrations/FlotillaDbContextModelSnapshot.cs @@ -521,24 +521,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Robots"); }); - modelBuilder.Entity("Api.Database.Models.RobotBatteryTimeseries", b => - { - b.Property("BatteryLevel") - .HasColumnType("real"); - - b.Property("MissionId") - .HasColumnType("text"); - - b.Property("RobotId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Time") - .HasColumnType("timestamp with time zone"); - - b.ToTable("RobotBatteryTimeseries"); - }); - modelBuilder.Entity("Api.Database.Models.RobotModel", b => { b.Property("Id") @@ -572,60 +554,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RobotModels"); }); - modelBuilder.Entity("Api.Database.Models.RobotPoseTimeseries", b => - { - b.Property("MissionId") - .HasColumnType("text"); - - b.Property("OrientationW") - .HasColumnType("real"); - - b.Property("OrientationX") - .HasColumnType("real"); - - b.Property("OrientationY") - .HasColumnType("real"); - - b.Property("OrientationZ") - .HasColumnType("real"); - - b.Property("PositionX") - .HasColumnType("real"); - - b.Property("PositionY") - .HasColumnType("real"); - - b.Property("PositionZ") - .HasColumnType("real"); - - b.Property("RobotId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Time") - .HasColumnType("timestamp with time zone"); - - b.ToTable("RobotPoseTimeseries"); - }); - - modelBuilder.Entity("Api.Database.Models.RobotPressureTimeseries", b => - { - b.Property("MissionId") - .HasColumnType("text"); - - b.Property("Pressure") - .HasColumnType("real"); - - b.Property("RobotId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Time") - .HasColumnType("timestamp with time zone"); - - b.ToTable("RobotPressureTimeseries"); - }); - modelBuilder.Entity("Api.Database.Models.Source", b => { b.Property("Id") 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); - } - } -}