diff --git a/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs
index efc729b54..57f712e6e 100644
--- a/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs
+++ b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs
@@ -39,6 +39,8 @@ internal abstract class ConfigHandlerBase : ConfigHandler
internal const string MetricExporterRefreshIntervalKey = "Metrics:ExporterRefreshIntervalInSeconds";
internal const string MetricLoaderRefreshIntervalKey = "Metrics:LoaderRefreshIntervalInMinutes";
+ internal const string SyncDatabaseJobRefreshIntervalKey = "Job:SyncDatabaseJobRefreshIntervalInMinutes";
+
internal const string IdeasEnabledKey = "Ideas:Enabled";
internal const string IdeasUrlKey = "Ideas:Url";
internal const string IdeasAuthenticationHeaderKey = "Ideas:Authentication:Header";
diff --git a/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs b/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs
index 1b0b52708..2c234eb65 100644
--- a/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs
+++ b/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs
@@ -16,6 +16,8 @@ internal DevelopmentConfigHandler(IConfiguration config)
public override string PortalName => this.config[PortalNameKey];
+ public override int SyncDatabaseJobRefreshIntervalInMinutes => this.config.GetValue(SyncDatabaseJobRefreshIntervalKey, 5);
+
public override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30);
public override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10);
diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220922124300_Add Device and LorawanDevice.Designer.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220922124300_Add Device and LorawanDevice.Designer.cs
new file mode 100644
index 000000000..b2db44d9c
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220922124300_Add Device and LorawanDevice.Designer.cs
@@ -0,0 +1,344 @@
+//
+using System;
+using AzureIoTHub.Portal.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace AzureIoTHub.Portal.Infrastructure.Migrations
+{
+ [DbContext(typeof(PortalDbContext))]
+ [Migration("20220922124300_Add Device and LorawanDevice")]
+ partial class AddDeviceandLorawanDevice
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Device", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("DeviceModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsConnected")
+ .HasColumnType("boolean");
+
+ b.Property("IsEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StatusUpdatedTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Tags")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Version")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Devices");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModel", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ABPRelaxMode")
+ .HasColumnType("boolean");
+
+ b.Property("AppEUI")
+ .HasColumnType("text");
+
+ b.Property("Deduplication")
+ .HasColumnType("integer");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Downlink")
+ .HasColumnType("boolean");
+
+ b.Property("IsBuiltin")
+ .HasColumnType("boolean");
+
+ b.Property("KeepAliveTimeout")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PreferredWindow")
+ .HasColumnType("integer");
+
+ b.Property("RXDelay")
+ .HasColumnType("integer");
+
+ b.Property("SensorDecoder")
+ .HasColumnType("text");
+
+ b.Property("SupportLoRaFeatures")
+ .HasColumnType("boolean");
+
+ b.Property("UseOTAA")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.ToTable("DeviceModels");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModelCommand", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Confirmed")
+ .HasColumnType("boolean");
+
+ b.Property("DeviceModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Frame")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsBuiltin")
+ .HasColumnType("boolean");
+
+ b.Property("Port")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("DeviceModelCommands");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModelProperty", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsWritable")
+ .HasColumnType("boolean");
+
+ b.Property("ModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Order")
+ .HasColumnType("integer");
+
+ b.Property("PropertyType")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("DeviceModelProperties");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceTag", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Label")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Required")
+ .HasColumnType("boolean");
+
+ b.Property("Searchable")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.ToTable("DeviceTags");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.EdgeDeviceModel", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("EdgeDeviceModels");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.EdgeDeviceModelCommand", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("EdgeDeviceModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("EdgeDeviceModelCommands");
+ });
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.LorawanDevice", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ABPRelaxMode")
+ .HasColumnType("boolean");
+
+ b.Property("AlreadyLoggedInOnce")
+ .HasColumnType("boolean");
+
+ b.Property("AppEUI")
+ .HasColumnType("text");
+
+ b.Property("AppKey")
+ .HasColumnType("text");
+
+ b.Property("AppSKey")
+ .HasColumnType("text");
+
+ b.Property("ClassType")
+ .HasColumnType("integer");
+
+ b.Property("DataRate")
+ .HasColumnType("text");
+
+ b.Property("Deduplication")
+ .HasColumnType("integer");
+
+ b.Property("DevAddr")
+ .HasColumnType("text");
+
+ b.Property("DeviceModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Downlink")
+ .HasColumnType("boolean");
+
+ b.Property("FCntDownStart")
+ .HasColumnType("integer");
+
+ b.Property("FCntResetCounter")
+ .HasColumnType("integer");
+
+ b.Property("FCntUpStart")
+ .HasColumnType("integer");
+
+ b.Property("GatewayID")
+ .HasColumnType("text");
+
+ b.Property("IsConnected")
+ .HasColumnType("boolean");
+
+ b.Property("IsEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("KeepAliveTimeout")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NbRep")
+ .HasColumnType("text");
+
+ b.Property("NwkSKey")
+ .HasColumnType("text");
+
+ b.Property("PreferredWindow")
+ .HasColumnType("integer");
+
+ b.Property("RX1DROffset")
+ .HasColumnType("integer");
+
+ b.Property("RX2DataRate")
+ .HasColumnType("integer");
+
+ b.Property("RXDelay")
+ .HasColumnType("integer");
+
+ b.Property("ReportedRX1DROffset")
+ .HasColumnType("text");
+
+ b.Property("ReportedRX2DataRate")
+ .HasColumnType("text");
+
+ b.Property("ReportedRXDelay")
+ .HasColumnType("text");
+
+ b.Property("SensorDecoder")
+ .HasColumnType("text");
+
+ b.Property("StatusUpdatedTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Supports32BitFCnt")
+ .HasColumnType("boolean");
+
+ b.Property("Tags")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TxPower")
+ .HasColumnType("text");
+
+ b.Property("UseOTAA")
+ .HasColumnType("boolean");
+
+ b.Property("Version")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("LorawanDevices");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220922124300_Add Device and LorawanDevice.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220922124300_Add Device and LorawanDevice.cs
new file mode 100644
index 000000000..b12375418
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220922124300_Add Device and LorawanDevice.cs
@@ -0,0 +1,89 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+#nullable disable
+
+namespace AzureIoTHub.Portal.Infrastructure.Migrations
+{
+ using System;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
+ public partial class AddDeviceandLorawanDevice : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ _ = migrationBuilder.CreateTable(
+ name: "Devices",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ DeviceModelId = table.Column(type: "text", nullable: false),
+ IsConnected = table.Column(type: "boolean", nullable: false),
+ IsEnabled = table.Column(type: "boolean", nullable: false),
+ Version = table.Column(type: "integer", nullable: false),
+ StatusUpdatedTime = table.Column(type: "timestamp with time zone", nullable: false),
+ Tags = table.Column(type: "text", nullable: false)
+ },
+ constraints: table =>
+ {
+ _ = table.PrimaryKey("PK_Devices", x => x.Id);
+ });
+
+ _ = migrationBuilder.CreateTable(
+ name: "LorawanDevices",
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ DeviceModelId = table.Column(type: "text", nullable: false),
+ IsConnected = table.Column(type: "boolean", nullable: false),
+ IsEnabled = table.Column(type: "boolean", nullable: false),
+ Version = table.Column(type: "integer", nullable: false),
+ StatusUpdatedTime = table.Column(type: "timestamp with time zone", nullable: false),
+ Tags = table.Column(type: "text", nullable: false),
+ UseOTAA = table.Column(type: "boolean", nullable: false),
+ AppKey = table.Column(type: "text", nullable: true),
+ AppEUI = table.Column(type: "text", nullable: true),
+ AppSKey = table.Column(type: "text", nullable: true),
+ NwkSKey = table.Column(type: "text", nullable: true),
+ DevAddr = table.Column(type: "text", nullable: true),
+ AlreadyLoggedInOnce = table.Column(type: "boolean", nullable: false),
+ DataRate = table.Column(type: "text", nullable: true),
+ TxPower = table.Column(type: "text", nullable: true),
+ NbRep = table.Column(type: "text", nullable: true),
+ ReportedRX2DataRate = table.Column(type: "text", nullable: true),
+ ReportedRX1DROffset = table.Column(type: "text", nullable: true),
+ ReportedRXDelay = table.Column(type: "text", nullable: true),
+ GatewayID = table.Column(type: "text", nullable: true),
+ Downlink = table.Column(type: "boolean", nullable: true),
+ ClassType = table.Column(type: "integer", nullable: false),
+ PreferredWindow = table.Column(type: "integer", nullable: false),
+ Deduplication = table.Column(type: "integer", nullable: false),
+ RX1DROffset = table.Column(type: "integer", nullable: true),
+ RX2DataRate = table.Column(type: "integer", nullable: true),
+ RXDelay = table.Column(type: "integer", nullable: true),
+ ABPRelaxMode = table.Column(type: "boolean", nullable: true),
+ FCntUpStart = table.Column(type: "integer", nullable: true),
+ FCntDownStart = table.Column(type: "integer", nullable: true),
+ FCntResetCounter = table.Column(type: "integer", nullable: true),
+ Supports32BitFCnt = table.Column(type: "boolean", nullable: true),
+ KeepAliveTimeout = table.Column(type: "integer", nullable: true),
+ SensorDecoder = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ _ = table.PrimaryKey("PK_LorawanDevices", x => x.Id);
+ });
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ _ = migrationBuilder.DropTable(
+ name: "Devices");
+
+ _ = migrationBuilder.DropTable(
+ name: "LorawanDevices");
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs
index ee7f83f1e..b80b1060f 100644
--- a/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs
+++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs
@@ -17,11 +17,45 @@ protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "6.0.8")
+ .HasAnnotation("ProductVersion", "6.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Device", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("DeviceModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsConnected")
+ .HasColumnType("boolean");
+
+ b.Property("IsEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StatusUpdatedTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Tags")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Version")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Devices");
+ });
+
modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModel", b =>
{
b.Property("Id")
@@ -188,6 +222,124 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("EdgeDeviceModelCommands");
});
+
+ modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.LorawanDevice", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ABPRelaxMode")
+ .HasColumnType("boolean");
+
+ b.Property("AlreadyLoggedInOnce")
+ .HasColumnType("boolean");
+
+ b.Property("AppEUI")
+ .HasColumnType("text");
+
+ b.Property("AppKey")
+ .HasColumnType("text");
+
+ b.Property("AppSKey")
+ .HasColumnType("text");
+
+ b.Property("ClassType")
+ .HasColumnType("integer");
+
+ b.Property("DataRate")
+ .HasColumnType("text");
+
+ b.Property("Deduplication")
+ .HasColumnType("integer");
+
+ b.Property("DevAddr")
+ .HasColumnType("text");
+
+ b.Property("DeviceModelId")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Downlink")
+ .HasColumnType("boolean");
+
+ b.Property("FCntDownStart")
+ .HasColumnType("integer");
+
+ b.Property("FCntResetCounter")
+ .HasColumnType("integer");
+
+ b.Property("FCntUpStart")
+ .HasColumnType("integer");
+
+ b.Property("GatewayID")
+ .HasColumnType("text");
+
+ b.Property("IsConnected")
+ .HasColumnType("boolean");
+
+ b.Property("IsEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("KeepAliveTimeout")
+ .HasColumnType("integer");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("NbRep")
+ .HasColumnType("text");
+
+ b.Property("NwkSKey")
+ .HasColumnType("text");
+
+ b.Property("PreferredWindow")
+ .HasColumnType("integer");
+
+ b.Property("RX1DROffset")
+ .HasColumnType("integer");
+
+ b.Property("RX2DataRate")
+ .HasColumnType("integer");
+
+ b.Property("RXDelay")
+ .HasColumnType("integer");
+
+ b.Property("ReportedRX1DROffset")
+ .HasColumnType("text");
+
+ b.Property("ReportedRX2DataRate")
+ .HasColumnType("text");
+
+ b.Property("ReportedRXDelay")
+ .HasColumnType("text");
+
+ b.Property("SensorDecoder")
+ .HasColumnType("text");
+
+ b.Property("StatusUpdatedTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Supports32BitFCnt")
+ .HasColumnType("boolean");
+
+ b.Property("Tags")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TxPower")
+ .HasColumnType("text");
+
+ b.Property("UseOTAA")
+ .HasColumnType("boolean");
+
+ b.Property("Version")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("LorawanDevices");
+ });
#pragma warning restore 612, 618
}
}
diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs
index adb8bca34..1eeefbcb6 100644
--- a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs
+++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs
@@ -9,6 +9,7 @@ namespace AzureIoTHub.Portal.Infrastructure
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Logging;
+ using Newtonsoft.Json;
public class PortalDbContext : DbContext
{
@@ -16,6 +17,8 @@ public class PortalDbContext : DbContext
public DbSet DeviceTags { get; set; }
public DbSet DeviceModelCommands { get; set; }
public DbSet DeviceModels { get; set; }
+ public DbSet Devices { get; set; }
+ public DbSet LorawanDevices { get; set; }
public DbSet EdgeDeviceModels { get; set; }
public DbSet EdgeDeviceModelCommands { get; set; }
@@ -25,5 +28,22 @@ public PortalDbContext(DbContextOptions options)
: base(options)
{
}
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ _ = modelBuilder.Entity()
+ .Property(b => b.Tags)
+ .HasConversion(
+ v => JsonConvert.SerializeObject(v),
+ v => JsonConvert.DeserializeObject>(v));
+
+ _ = modelBuilder.Entity()
+ .Property(b => b.Tags)
+ .HasConversion(
+ v => JsonConvert.SerializeObject(v),
+ v => JsonConvert.DeserializeObject>(v));
+ }
}
}
diff --git a/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs b/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs
index b2e423dd2..e8195773f 100644
--- a/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs
+++ b/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs
@@ -16,6 +16,8 @@ internal ProductionConfigHandler(IConfiguration config)
public override string PortalName => this.config[PortalNameKey];
+ public override int SyncDatabaseJobRefreshIntervalInMinutes => this.config.GetValue(SyncDatabaseJobRefreshIntervalKey, 5);
+
public override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30);
public override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10);
diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceRepository.cs
new file mode 100644
index 000000000..ae814479a
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceRepository.cs
@@ -0,0 +1,15 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Infrastructure.Repositories
+{
+ using AzureIoTHub.Portal.Domain.Repositories;
+ using Domain.Entities;
+
+ public class DeviceRepository : GenericRepository, IDeviceRepository
+ {
+ public DeviceRepository(PortalDbContext context) : base(context)
+ {
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/LorawanDeviceRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/LorawanDeviceRepository.cs
new file mode 100644
index 000000000..1ab71dd99
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/LorawanDeviceRepository.cs
@@ -0,0 +1,15 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Infrastructure.Repositories
+{
+ using AzureIoTHub.Portal.Domain.Repositories;
+ using Domain.Entities;
+
+ public class LorawanDeviceRepository : GenericRepository, ILorawanDeviceRepository
+ {
+ public LorawanDeviceRepository(PortalDbContext context) : base(context)
+ {
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs
index 7d389c50b..40a5773f4 100644
--- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs
+++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs
@@ -159,6 +159,16 @@ public void SettingsShouldGetBoolFromAppSettings(string configKey, string config
this.mockRepository.VerifyAll();
}
+ [Test]
+ public void SyncDatabaseJobRefreshIntervalInMinutesConfigMustHaveDefaultValue()
+ {
+ // Arrange
+ var developmentConfigHandler = new DevelopmentConfigHandler(new ConfigurationManager());
+
+ // Assert
+ _ = developmentConfigHandler.SyncDatabaseJobRefreshIntervalInMinutes.Should().Be(5);
+ }
+
[Test]
public void MetricExporterRefreshIntervalInSecondsConfigMustHaveDefaultValue()
{
diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs
index 5b9c09e1f..0a024780b 100644
--- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs
+++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs
@@ -3,10 +3,8 @@
namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure
{
- using AzureIoTHub.Portal.Server;
using System;
using System.Globalization;
- using System.Reflection;
using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Moq;
@@ -185,6 +183,16 @@ public void SettingsShouldGetBoolFromAppSettings(string configKey, string config
this.mockRepository.VerifyAll();
}
+ [Test]
+ public void SyncDatabaseJobRefreshIntervalInMinutesConfigMustHaveDefaultValue()
+ {
+ // Arrange
+ var productionConfigHandler = new ProductionConfigHandler(new ConfigurationManager());
+
+ // Assert
+ _ = productionConfigHandler.SyncDatabaseJobRefreshIntervalInMinutes.Should().Be(5);
+ }
+
[Test]
public void MetricExporterRefreshIntervalInSecondsConfigMustHaveDefaultValue()
{
diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceRepositoryTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceRepositoryTest.cs
new file mode 100644
index 000000000..412dfff07
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceRepositoryTest.cs
@@ -0,0 +1,43 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Repositories
+{
+ using System.Linq;
+ using System.Threading.Tasks;
+ using AutoFixture;
+ using AzureIoTHub.Portal.Domain.Entities;
+ using AzureIoTHub.Portal.Infrastructure.Repositories;
+ using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases;
+ using FluentAssertions;
+ using NUnit.Framework;
+
+ public class DeviceRepositoryTest : BackendUnitTest
+ {
+ private DeviceRepository deviceRepository;
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ this.deviceRepository = new DeviceRepository(DbContext);
+ }
+
+ [Test]
+ public async Task GetAllShouldReturnExpectedDevices()
+ {
+ // Arrange
+ var expectedDevices = Fixture.CreateMany(5).ToList();
+
+ await DbContext.AddRangeAsync(expectedDevices);
+
+ _ = await DbContext.SaveChangesAsync();
+
+ //Act
+ var result = this.deviceRepository.GetAll().ToList();
+
+ // Assert
+ _ = result.Should().BeEquivalentTo(expectedDevices);
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/LorawanDeviceRepositoryTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/LorawanDeviceRepositoryTest.cs
new file mode 100644
index 000000000..71aaecdae
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/LorawanDeviceRepositoryTest.cs
@@ -0,0 +1,43 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Repositories
+{
+ using AutoFixture;
+ using System.Threading.Tasks;
+ using AzureIoTHub.Portal.Infrastructure.Repositories;
+ using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases;
+ using NUnit.Framework;
+ using AzureIoTHub.Portal.Domain.Entities;
+ using System.Linq;
+ using FluentAssertions;
+
+ public class LorawanDeviceRepositoryTest : BackendUnitTest
+ {
+ private LorawanDeviceRepository lorawanDeviceRepository;
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ this.lorawanDeviceRepository = new LorawanDeviceRepository(DbContext);
+ }
+
+ [Test]
+ public async Task GetAllShouldReturnExpectedDevices()
+ {
+ // Arrange
+ var expectedDevices = Fixture.CreateMany(5).ToList();
+
+ await DbContext.AddRangeAsync(expectedDevices);
+
+ _ = await DbContext.SaveChangesAsync();
+
+ //Act
+ var result = this.lorawanDeviceRepository.GetAll().ToList();
+
+ // Assert
+ _ = result.Should().BeEquivalentTo(expectedDevices);
+ }
+ }
+}
diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDeviceJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDeviceJobTests.cs
new file mode 100644
index 000000000..d3b43a534
--- /dev/null
+++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDeviceJobTests.cs
@@ -0,0 +1,304 @@
+// Copyright (c) CGI France. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace AzureIoTHub.Portal.Tests.Unit.Server.Jobs
+{
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using AutoFixture;
+ using AzureIoTHub.Portal.Domain;
+ using AzureIoTHub.Portal.Domain.Repositories;
+ using AzureIoTHub.Portal.Server.Jobs;
+ using AzureIoTHub.Portal.Server.Services;
+ using Microsoft.Azure.Devices.Shared;
+ using Microsoft.Extensions.DependencyInjection;
+ using Moq;
+ using NUnit.Framework;
+ using Portal.Domain.Entities;
+ using Quartz;
+ using UnitTests.Bases;
+
+ public class SyncDeviceJobTests : BackendUnitTest
+ {
+ private IJob syncDevicesJob;
+
+ private Mock mockDeviceService;
+ private Mock mockDeviceModelRepository;
+ private Mock mockLorawanDeviceRepository;
+ private Mock mockDeviceRepository;
+ private Mock mockUnitOfWork;
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ this.mockDeviceService = MockRepository.Create();
+ this.mockDeviceModelRepository = MockRepository.Create();
+ this.mockLorawanDeviceRepository = MockRepository.Create();
+ this.mockDeviceRepository = MockRepository.Create();
+ this.mockUnitOfWork = MockRepository.Create();
+
+
+ _ = ServiceCollection.AddSingleton(this.mockDeviceService.Object);
+ _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object);
+ _ = ServiceCollection.AddSingleton(this.mockLorawanDeviceRepository.Object);
+ _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object);
+ _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object);
+ _ = ServiceCollection.AddSingleton();
+
+ Services = ServiceCollection.BuildServiceProvider();
+
+ this.syncDevicesJob = Services.GetRequiredService();
+ }
+
+ [Test]
+ public async Task Execute_NewDevice_DeviceCreated()
+ {
+ // Arrange
+ var mockJobExecutionContext = MockRepository.Create();
+
+ var expectedDeviceModel = Fixture.Create();
+ expectedDeviceModel.SupportLoRaFeatures = false;
+
+ var expectedTwinDevice = new Twin
+ {
+ DeviceId = Fixture.Create(),
+ Tags = new TwinCollection
+ {
+ ["modelId"] = expectedDeviceModel.Id,
+ ["deviceName"] = Fixture.Create()
+ }
+ };
+
+ _ = this.mockDeviceService
+ .Setup(x => x.GetAllDevice(
+ It.IsAny(),
+ It.IsAny(),
+ It.Is(x => x == "LoRa Concentrator"),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>(),
+ It.Is(x => x == 100)))
+ .ReturnsAsync(new PaginationResult
+ {
+ Items = new List
+ {
+ expectedTwinDevice
+ },
+ TotalItems = 1
+ });
+
+
+ _ = this.mockDeviceModelRepository
+ .Setup(x => x.GetByIdAsync(expectedDeviceModel.Id))
+ .ReturnsAsync(expectedDeviceModel);
+
+ _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(expectedTwinDevice.DeviceId))
+ .ReturnsAsync((Device)null);
+
+ _ = this.mockDeviceRepository.Setup(repository => repository.InsertAsync(It.IsAny()))
+ .Returns(Task.CompletedTask);
+
+ _ = this.mockUnitOfWork.Setup(work => work.SaveAsync())
+ .Returns(Task.CompletedTask);
+
+ // Act
+ await this.syncDevicesJob.Execute(mockJobExecutionContext.Object);
+
+ // Assert
+ MockRepository.VerifyAll();
+ }
+
+ [Test]
+ public async Task Execute_ExistingDeviceWithGreeterVersion_DeviceUpdated()
+ {
+ // Arrange
+ var mockJobExecutionContext = MockRepository.Create();
+
+ var expectedDeviceModel = Fixture.Create();
+ expectedDeviceModel.SupportLoRaFeatures = false;
+
+ var expectedTwinDevice = new Twin
+ {
+ DeviceId = Fixture.Create(),
+ Tags = new TwinCollection
+ {
+ ["modelId"] = expectedDeviceModel.Id,
+ ["deviceName"] = Fixture.Create()
+ },
+ Version = 2
+ };
+
+ var existingDevice = new Device
+ {
+ Id = expectedTwinDevice.DeviceId,
+ Version = 1
+ };
+
+ _ = this.mockDeviceService
+ .Setup(x => x.GetAllDevice(
+ It.IsAny(),
+ It.IsAny(),
+ It.Is(x => x == "LoRa Concentrator"),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>(),
+ It.Is(x => x == 100)))
+ .ReturnsAsync(new PaginationResult
+ {
+ Items = new List
+ {
+ expectedTwinDevice
+ },
+ TotalItems = 1
+ });
+
+
+ _ = this.mockDeviceModelRepository
+ .Setup(x => x.GetByIdAsync(expectedDeviceModel.Id))
+ .ReturnsAsync(expectedDeviceModel);
+
+ _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(expectedTwinDevice.DeviceId))
+ .ReturnsAsync(existingDevice);
+
+ this.mockDeviceRepository.Setup(repository => repository.Update(It.IsAny()))
+ .Verifiable();
+
+ _ = this.mockUnitOfWork.Setup(work => work.SaveAsync())
+ .Returns(Task.CompletedTask);
+
+ // Act
+ await this.syncDevicesJob.Execute(mockJobExecutionContext.Object);
+
+ // Assert
+ MockRepository.VerifyAll();
+ }
+
+ [Test]
+ public async Task Execute_NewLorawanDevice_LorawanDeviceCreated()
+ {
+ // Arrange
+ var mockJobExecutionContext = MockRepository.Create();
+
+ var expectedDeviceModel = Fixture.Create();
+ expectedDeviceModel.SupportLoRaFeatures = true;
+
+ var expectedTwinDevice = new Twin
+ {
+ DeviceId = Fixture.Create(),
+ Tags = new TwinCollection
+ {
+ ["modelId"] = expectedDeviceModel.Id,
+ ["deviceName"] = Fixture.Create()
+ }
+ };
+
+ _ = this.mockDeviceService
+ .Setup(x => x.GetAllDevice(
+ It.IsAny(),
+ It.IsAny(),
+ It.Is(x => x == "LoRa Concentrator"),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>(),
+ It.Is(x => x == 100)))
+ .ReturnsAsync(new PaginationResult
+ {
+ Items = new List
+ {
+ expectedTwinDevice
+ },
+ TotalItems = 1
+ });
+
+
+ _ = this.mockDeviceModelRepository
+ .Setup(x => x.GetByIdAsync(expectedDeviceModel.Id))
+ .ReturnsAsync(expectedDeviceModel);
+
+ _ = this.mockLorawanDeviceRepository.Setup(repository => repository.GetByIdAsync(expectedTwinDevice.DeviceId))
+ .ReturnsAsync((LorawanDevice)null);
+
+ _ = this.mockLorawanDeviceRepository.Setup(repository => repository.InsertAsync(It.IsAny()))
+ .Returns(Task.CompletedTask);
+
+ _ = this.mockUnitOfWork.Setup(work => work.SaveAsync())
+ .Returns(Task.CompletedTask);
+
+ // Act
+ await this.syncDevicesJob.Execute(mockJobExecutionContext.Object);
+
+ // Assert
+ MockRepository.VerifyAll();
+ }
+
+ [Test]
+ public async Task Execute_ExistingLorawanDeviceWithGreeterVersion_LorawanDeviceUpdated()
+ {
+ // Arrange
+ var mockJobExecutionContext = MockRepository.Create();
+
+ var expectedDeviceModel = Fixture.Create();
+ expectedDeviceModel.SupportLoRaFeatures = true;
+
+ var expectedTwinDevice = new Twin
+ {
+ DeviceId = Fixture.Create(),
+ Tags = new TwinCollection
+ {
+ ["modelId"] = expectedDeviceModel.Id,
+ ["deviceName"] = Fixture.Create()
+ },
+ Version = 2
+ };
+
+ var existingLorawanDevice = new LorawanDevice
+ {
+ Id = expectedTwinDevice.DeviceId,
+ Version = 1
+ };
+
+ _ = this.mockDeviceService
+ .Setup(x => x.GetAllDevice(
+ It.IsAny(),
+ It.IsAny(),
+ It.Is(x => x == "LoRa Concentrator"),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny(),
+ It.IsAny>(),
+ It.Is