diff --git a/Common/CommonServiceLibrary.BlobStorage/CommonServiceLibrary.BlobStorage.csproj b/Common/CommonServiceLibrary.BlobStorage/CommonServiceLibrary.BlobStorage.csproj
deleted file mode 100644
index 3748767..0000000
--- a/Common/CommonServiceLibrary.BlobStorage/CommonServiceLibrary.BlobStorage.csproj
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
diff --git a/Common/CommonServiceLibrary.BlobStorage/GlobalUsings.cs b/Common/CommonServiceLibrary.BlobStorage/GlobalUsings.cs
deleted file mode 100644
index d9c576f..0000000
--- a/Common/CommonServiceLibrary.BlobStorage/GlobalUsings.cs
+++ /dev/null
@@ -1,2 +0,0 @@
-global using Azure.Storage.Blobs;
-global using Microsoft.Extensions.Configuration;
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.BlobStorage/RaportContainer.cs b/Common/CommonServiceLibrary.BlobStorage/RaportContainer.cs
deleted file mode 100644
index 6b176df..0000000
--- a/Common/CommonServiceLibrary.BlobStorage/RaportContainer.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-public class RaportContainer
-{
- private IConfiguration _configuration;
- private BlobServiceClient _blobServiceClient;
- private BlobContainerClient _containerClient;
-
- public RaportContainer(IConfiguration configuration)
- {
- _configuration = configuration;
-
- string connectionString = _configuration.GetValue("AzureBlob:ConnectionString");
- string containerName = _configuration.GetValue("AzureBlob:ContainerName");
-
- _blobServiceClient = new BlobServiceClient(connectionString);
- _containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
- }
-
- //public async Task UploadDocumentAsync(Document document)
- //{
- // if (document == null)
- // {
- // throw new Exception();
- // }
-
- // string title = $"{document.GetMetadata().Title!}.pdf";
-
- // using var stream = new MemoryStream();
- // document.GeneratePdf(stream);
-
- // stream.Position = 0;
- // var response = await _containerClient.UploadBlobAsync(title, stream);
-
- // return true;
- //}
-
- //public async Task GetExistingRaportsNames()
- //{
- // List names = new List();
- // var blobs = _containerClient.GetBlobsAsync().AsPages();
-
- // await foreach (var blob in blobs)
- // {
- // foreach (var item in blob.Values)
- // {
- // names.Add(item.Name);
- // }
- // }
-
- // return names.ToArray();
- //}
-}
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/CommonServiceLibrary.GRPC.csproj b/Common/CommonServiceLibrary.GRPC/CommonServiceLibrary.GRPC.csproj
index 6fa5a84..f31f02e 100644
--- a/Common/CommonServiceLibrary.GRPC/CommonServiceLibrary.GRPC.csproj
+++ b/Common/CommonServiceLibrary.GRPC/CommonServiceLibrary.GRPC.csproj
@@ -23,11 +23,6 @@
-
-
-
-
-
diff --git a/Common/CommonServiceLibrary.GRPC/DependencyInjection.cs b/Common/CommonServiceLibrary.GRPC/DependencyInjection.cs
index 0b48341..7a8df8f 100644
--- a/Common/CommonServiceLibrary.GRPC/DependencyInjection.cs
+++ b/Common/CommonServiceLibrary.GRPC/DependencyInjection.cs
@@ -5,76 +5,82 @@ public static class DependencyInjection
public static IServiceCollection AddGRPCMappings(this IServiceCollection services)
{
// Devices
- TypeAdapterConfig
+ TypeAdapterConfig
.NewConfig()
.Map(dest => dest.ID, src => src.Id)
.Map(dest => dest.Name, src => src.Name)
.Map(dest => dest.DeviceNumber, src => src.DeviceNumber)
.Map(dest => dest.RegisterDate, src => src.RegisterDate)
- .Map(dest => dest.LocationID, src => src.LocationId)
.Map(dest => dest.Location, src => src.Location)
- .Map(dest => dest.TimestampID, src => src.TimestampConfigurationId)
.Map(dest => dest.Timestamp, src => src.TimestampConfiguration)
- .Map(dest => dest.StatusID, src => src.StatusId)
.Map(dest => dest.Status, src => src.Status);
- TypeAdapterConfig
+ TypeAdapterConfig
.NewConfig()
.Map(dest => dest.ID, src => src.Id)
.Map(dest => dest.Cron, src => src.Cron);
- TypeAdapterConfig
+ TypeAdapterConfig
.NewConfig()
.Map(dest => dest.ID, src => src.Id)
+ .Map(dest => dest.Hash, src => src.Hash)
.Map(dest => dest.Name, src => src.Name);
- TypeAdapterConfig
+ TypeAdapterConfig
.NewConfig()
.Map(dest => dest.ID, src => src.Id)
.Map(dest => dest.Type, src => src.Type);
- //TypeAdapterConfig
- // .NewConfig()
- // .Map(dest => dest.ID, src => src.Id)
- // .Map(dest => dest.Temperature, src => src.Temperature)
- // .Map(dest => dest.Humidity, src => src.Humidity)
- // .Map(dest => dest.CarbonDioxide, src => src.CarbonDioxide)
- // .Map(dest => dest.VolatileOrganicCompounds, src => src.VolatileOrganicCompounds)
- // .Map(dest => dest.PM1, src => src.Pm1)
- // .Map(dest => dest.PM25, src => src.Pm25)
- // .Map(dest => dest.PM10, src => src.Pm10)
- // .Map(dest => dest.Formaldehyde, src => src.Formaldehyde)
- // .Map(dest => dest.CarbonMonoxide, src => src.CarbonMonoxide)
- // .Map(dest => dest.Ozone, src => src.Ozone)
- // .Map(dest => dest.Ammonia, src => src.Ammonia)
- // .Map(dest => dest.Airflow, src => src.Airflow)
- // .Map(dest => dest.AirIonizationLevel, src => src.AirIonizationLevel)
- // .Map(dest => dest.Oxygen, src => src.Oxygen)
- // .Map(dest => dest.Radon, src => src.Radon)
- // .Map(dest => dest.Illuminance, src => src.Illuminance)
- // .Map(dest => dest.SoundLevel, src => src.SoundLevel);
+ // gRPC client mapping for Measurement
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.Id)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.Co2)
+ .Map(x => x.VolatileOrganicCompounds, y => y.Voc)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2V5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.Co)
+ .Map(x => x.Ozone, y => y.O3)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.O2)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.Id, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(dest => dest.MeasurementCaptureDate, src => src.MeasurementCaptureDate.ToString("o"))
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.Co2, y => y.CarbonDioxide)
+ .Map(x => x.Voc, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2V5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.Co, y => y.CarbonMonoxide)
+ .Map(x => x.O3, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.O2, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
- //// Meaesurements
- //TypeAdapterConfig
- // .NewConfig()
- // .Map(dest => dest.ID, src => src.Id)
- // .Map(dest => dest.DeviceNumber, src => src.DeviceNumber)
- // .Map(dest => dest.Temperature, src => src.Temperature)
- // .Map(dest => dest.Humidity, src => src.Humidity)
- // .Map(dest => dest.CarbonDioxide, src => src.Co2)
- // .Map(dest => dest.VolatileOrganicCompounds, src => src.Voc)
- // .Map(dest => dest.ParticulateMatter1, src => src.ParticulateMatter1)
- // .Map(dest => dest.ParticulateMatter2v5, src => src.ParticulateMatter2V5)
- // .Map(dest => dest.ParticulateMatter10, src => src.ParticulateMatter10)
- // .Map(dest => dest.Formaldehyde, src => src.Formaldehyde)
- // .Map(dest => dest.CarbonMonoxide, src => src.Co)
- // .Map(dest => dest.Ammonia, src => src.Ammonia)
- // .Map(dest => dest.Airflow, src => src.Airflow)
- // .Map(dest => dest.AirIonizationLevel, src => src.AirIonizationLevel)
- // .Map(dest => dest.Oxygen, src => src.O2)
- // .Map(dest => dest.Radon, src => src.Radon)
- // .Map(dest => dest.Illuminance, src => src.Illuminance)
- // .Map(dest => dest.SoundLevel, src => src.SoundLevel);
return services;
}
diff --git a/Common/CommonServiceLibrary.GRPC/GlobalUsings.cs b/Common/CommonServiceLibrary.GRPC/GlobalUsings.cs
index 5945f05..e7bb94c 100644
--- a/Common/CommonServiceLibrary.GRPC/GlobalUsings.cs
+++ b/Common/CommonServiceLibrary.GRPC/GlobalUsings.cs
@@ -1,7 +1,7 @@
-global using Devices.Domain.Models;
+global using CommonServiceLibrary.GRPC.Types.Devices;
+global using CommonServiceLibrary.GRPC.Types.Measurements;
global using Devices.GRPCClient;
global using Grpc.Core;
global using Mapster;
-global using Measurements.Domain.Models;
global using Measurements.GRPCClient;
global using Microsoft.Extensions.DependencyInjection;
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Protos/devices.proto b/Common/CommonServiceLibrary.GRPC/Protos/devices.proto
index e344e6c..dea058b 100644
--- a/Common/CommonServiceLibrary.GRPC/Protos/devices.proto
+++ b/Common/CommonServiceLibrary.GRPC/Protos/devices.proto
@@ -8,6 +8,11 @@ service DevicesService {
rpc GetDeviceByDeviceNumber (DeviceRequest) returns (GrpcDeviceModel);
rpc GetAllDevices (DeviceAllRequest) returns (stream GrpcDeviceModel);
rpc GetAllLocations (LocationAllRequest) returns (stream GrpcLocationModel);
+ rpc GetLocationByName (LocationByNameRequest) returns (GrpcLocationModel);
+}
+
+message LocationByNameRequest{
+ string name = 1;
}
message LocationAllRequest{
@@ -25,6 +30,7 @@ message DeviceRequest{
message GrpcLocationModel {
int32 id = 1;
string name = 2;
+ string hash = 3;
}
message GrpcTimestampConfigurationModel {
diff --git a/Common/CommonServiceLibrary.GRPC/Protos/measurements.proto b/Common/CommonServiceLibrary.GRPC/Protos/measurements.proto
index 5871082..d4ef2c0 100644
--- a/Common/CommonServiceLibrary.GRPC/Protos/measurements.proto
+++ b/Common/CommonServiceLibrary.GRPC/Protos/measurements.proto
@@ -5,48 +5,39 @@ option csharp_namespace = "Measurements.GRPCClient";
package measurements;
service MeasurementService {
- rpc MeasurementByID (MeasurementRequest) returns (MeasurementGrpcModel);
- rpc MeasurementsAllByDay (MeasurementByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsAllByWeek (MeasurementByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsAllByMonth (MeasurementByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsByDay (MeasurementFromDeviceByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsByWeek (MeasurementFromDeviceByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsByMonth (MeasurementFromDeviceByDateRequest) returns (stream MeasurementGrpcModel);
+ rpc MeasurementsQuery (MeasurementsQueryRequest) returns (stream MeasurementGrpcModel);
}
-message MeasurementByDateRequest {
- string date = 1;
-}
+message MeasurementsQueryRequest {
+ string sort_order = 1;
+ string start_date = 2;
+ string end_date = 3;
-message MeasurementFromDeviceByDateRequest {
- string device_number = 1;
- string date = 2;
-}
-
-message MeasurementRequest {
- string id = 1;
+ repeated string device_numbers = 4;
+ repeated string location_ids = 5;
}
message MeasurementGrpcModel {
string id = 1;
string device_number = 2;
- string register_date = 3;
-
- optional double temperature = 4;
- optional double humidity = 5;
- optional double co2 = 6;
- optional double voc = 7;
- optional double particulate_matter1 = 8;
- optional double particulate_matter2v5 = 9;
- optional double particulate_matter10 = 10;
- optional double formaldehyde = 11;
- optional double co = 12;
- optional double o3 = 13;
- optional double ammonia = 14;
- optional double airflow = 15;
- optional double air_ionization_level = 16;
- optional double o2 = 17;
- optional double radon = 18;
- optional double illuminance = 19;
- optional double sound_level = 20;
+ string measurement_capture_date = 3;
+ string location_hash = 4;
+
+ optional double temperature = 5;
+ optional double humidity = 6;
+ optional double co2 = 7;
+ optional double voc = 8;
+ optional double particulate_matter1 = 9;
+ optional double particulate_matter2v5 = 10;
+ optional double particulate_matter10 = 11;
+ optional double formaldehyde = 12;
+ optional double co = 13;
+ optional double o3 = 14;
+ optional double ammonia = 15;
+ optional double airflow = 16;
+ optional double air_ionization_level = 17;
+ optional double o2 = 18;
+ optional double radon = 19;
+ optional double illuminance = 20;
+ optional double sound_level = 21;
}
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Types/Devices/DeviceGRPC.cs b/Common/CommonServiceLibrary.GRPC/Types/Devices/DeviceGRPC.cs
new file mode 100644
index 0000000..eb485f2
--- /dev/null
+++ b/Common/CommonServiceLibrary.GRPC/Types/Devices/DeviceGRPC.cs
@@ -0,0 +1,12 @@
+namespace CommonServiceLibrary.GRPC.Types.Devices;
+
+public record DeviceGRPC(
+ int ID,
+ string Name,
+ Guid DeviceNumber,
+ DateTime RegisterDate,
+ LocationGRPC Location,
+ TimestampGRPC Timestamp,
+ ICollection MeasurementTypes,
+ StatusGRPC Status
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Types/Devices/LocationGRPC.cs b/Common/CommonServiceLibrary.GRPC/Types/Devices/LocationGRPC.cs
new file mode 100644
index 0000000..afed5ac
--- /dev/null
+++ b/Common/CommonServiceLibrary.GRPC/Types/Devices/LocationGRPC.cs
@@ -0,0 +1,7 @@
+namespace CommonServiceLibrary.GRPC.Types.Devices;
+
+public record LocationGRPC(
+ int ID,
+ string Name,
+ Guid Hash
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Types/Devices/MeasurementTypeGRPC.cs b/Common/CommonServiceLibrary.GRPC/Types/Devices/MeasurementTypeGRPC.cs
new file mode 100644
index 0000000..422b5dd
--- /dev/null
+++ b/Common/CommonServiceLibrary.GRPC/Types/Devices/MeasurementTypeGRPC.cs
@@ -0,0 +1,7 @@
+namespace CommonServiceLibrary.GRPC.Types.Devices;
+
+public record MeasurementTypeGRPC(
+ int ID,
+ string Name,
+ string Unit
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Types/Devices/StatusGRPC.cs b/Common/CommonServiceLibrary.GRPC/Types/Devices/StatusGRPC.cs
new file mode 100644
index 0000000..7f33441
--- /dev/null
+++ b/Common/CommonServiceLibrary.GRPC/Types/Devices/StatusGRPC.cs
@@ -0,0 +1,6 @@
+namespace CommonServiceLibrary.GRPC.Types.Devices;
+
+public record StatusGRPC(
+ int ID,
+ string Type
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Types/Devices/TimestampGRPC.cs b/Common/CommonServiceLibrary.GRPC/Types/Devices/TimestampGRPC.cs
new file mode 100644
index 0000000..6fe755d
--- /dev/null
+++ b/Common/CommonServiceLibrary.GRPC/Types/Devices/TimestampGRPC.cs
@@ -0,0 +1,6 @@
+namespace CommonServiceLibrary.GRPC.Types.Devices;
+
+public record TimestampGRPC(
+ int ID,
+ string Cron
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.GRPC/Types/Measurements/MeasurementGRPC.cs b/Common/CommonServiceLibrary.GRPC/Types/Measurements/MeasurementGRPC.cs
new file mode 100644
index 0000000..71fcc27
--- /dev/null
+++ b/Common/CommonServiceLibrary.GRPC/Types/Measurements/MeasurementGRPC.cs
@@ -0,0 +1,25 @@
+namespace CommonServiceLibrary.GRPC.Types.Measurements;
+
+public record MeasurementGRPC(
+ Guid ID,
+ Guid DeviceNumber,
+ DateTime MeasurementCaptureDate,
+ Guid LocationHash,
+ double? Temperature,
+ double? Humidity,
+ double? CarbonDioxide,
+ double? VolatileOrganicCompounds,
+ double? ParticulateMatter1,
+ double? ParticulateMatter2v5,
+ double? ParticulateMatter10,
+ double? Formaldehyde,
+ double? CarbonMonoxide,
+ double? Ozone,
+ double? Ammonia,
+ double? Airflow,
+ double? AirIonizationLevel,
+ double? Oxygen,
+ double? Radon,
+ double? Illuminance,
+ double? SoundLevel
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/CommonServiceLibrary.Messaging.csproj b/Common/CommonServiceLibrary.Messaging/CommonServiceLibrary.Messaging.csproj
index f3a458e..e952f25 100644
--- a/Common/CommonServiceLibrary.Messaging/CommonServiceLibrary.Messaging.csproj
+++ b/Common/CommonServiceLibrary.Messaging/CommonServiceLibrary.Messaging.csproj
@@ -10,9 +10,4 @@
-
-
-
-
-
diff --git a/Common/CommonServiceLibrary.Messaging/GlobalUsings.cs b/Common/CommonServiceLibrary.Messaging/GlobalUsings.cs
index 4b49f09..b58d697 100644
--- a/Common/CommonServiceLibrary.Messaging/GlobalUsings.cs
+++ b/Common/CommonServiceLibrary.Messaging/GlobalUsings.cs
@@ -1,2 +1,2 @@
-global using Devices.DataTransferObjects;
-global using Measurements.DataTransferObjects;
\ No newline at end of file
+global using CommonServiceLibrary.Messaging.Messages.DevicesService;
+global using CommonServiceLibrary.Messaging.Messages.MeasurementsService;
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultDevice.cs b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultDevice.cs
new file mode 100644
index 0000000..4903db4
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultDevice.cs
@@ -0,0 +1,12 @@
+namespace CommonServiceLibrary.Messaging.Messages.DevicesService;
+
+public record DevicesMessage_DefaultDevice(
+ int ID,
+ string Name,
+ Guid DeviceNumber,
+ DateTime RegisterDate,
+ DevicesMessage_DefaultLocation Location,
+ DevicesMessage_DefaultTimestamp Timestamp,
+ ICollection MeasurementTypes,
+ DevicesMessage_DefaultStatus Status
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultLocation.cs b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultLocation.cs
new file mode 100644
index 0000000..eb17f8b
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultLocation.cs
@@ -0,0 +1,7 @@
+namespace CommonServiceLibrary.Messaging.Messages.DevicesService;
+
+public record DevicesMessage_DefaultLocation(
+ int ID,
+ string Name,
+ Guid Hash
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultMeasurementType.cs b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultMeasurementType.cs
new file mode 100644
index 0000000..554f9d1
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultMeasurementType.cs
@@ -0,0 +1,7 @@
+namespace CommonServiceLibrary.Messaging.Messages.DevicesService;
+
+public record DevicesMessage_DefaultMeasurementType(
+ int ID,
+ string Name,
+ string Unit
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultStatus.cs b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultStatus.cs
new file mode 100644
index 0000000..26ef522
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultStatus.cs
@@ -0,0 +1,6 @@
+namespace CommonServiceLibrary.Messaging.Messages.DevicesService;
+
+public record DevicesMessage_DefaultStatus(
+ int ID,
+ string Type
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultTimestamp.cs b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultTimestamp.cs
new file mode 100644
index 0000000..e931e91
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/DevicesService/DevicesMessage_DefaultTimestamp.cs
@@ -0,0 +1,6 @@
+namespace CommonServiceLibrary.Messaging.Messages.DevicesService;
+
+public record DevicesMessage_DefaultTimestamp(
+ int ID,
+ string Cron
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/MeasurementsService/MeasurementsMessage_CreateMeasurement.cs b/Common/CommonServiceLibrary.Messaging/Messages/MeasurementsService/MeasurementsMessage_CreateMeasurement.cs
new file mode 100644
index 0000000..5eb60d2
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/MeasurementsService/MeasurementsMessage_CreateMeasurement.cs
@@ -0,0 +1,25 @@
+namespace CommonServiceLibrary.Messaging.Messages.MeasurementsService;
+
+public record MeasurementsMessage_CreateMeasurement(
+ Guid ID,
+ Guid DeviceNumber,
+ DateTime MeasurementCaptureDate,
+ Guid LocationHash,
+ double? Temperature,
+ double? Humidity,
+ double? CarbonDioxide,
+ double? VolatileOrganicCompounds,
+ double? ParticulateMatter1,
+ double? ParticulateMatter2v5,
+ double? ParticulateMatter10,
+ double? Formaldehyde,
+ double? CarbonMonoxide,
+ double? Ozone,
+ double? Ammonia,
+ double? Airflow,
+ double? AirIonizationLevel,
+ double? Oxygen,
+ double? Radon,
+ double? Illuminance,
+ double? SoundLevel
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/Messages/MeasurementsService/MeasurementsMessage_DefaultMeasurement.cs b/Common/CommonServiceLibrary.Messaging/Messages/MeasurementsService/MeasurementsMessage_DefaultMeasurement.cs
new file mode 100644
index 0000000..fdd69c2
--- /dev/null
+++ b/Common/CommonServiceLibrary.Messaging/Messages/MeasurementsService/MeasurementsMessage_DefaultMeasurement.cs
@@ -0,0 +1,25 @@
+namespace CommonServiceLibrary.Messaging.Messages.MeasurementsService;
+
+public record MeasurementsMessage_DefaultMeasurement(
+ Guid ID,
+ Guid DeviceNumber,
+ DateTime MeasurementCaptureDate,
+ Guid LocationHash,
+ double? Temperature,
+ double? Humidity,
+ double? CarbonDioxide,
+ double? VolatileOrganicCompounds,
+ double? ParticulateMatter1,
+ double? ParticulateMatter2v5,
+ double? ParticulateMatter10,
+ double? Formaldehyde,
+ double? CarbonMonoxide,
+ double? Ozone,
+ double? Ammonia,
+ double? Airflow,
+ double? AirIonizationLevel,
+ double? Oxygen,
+ double? Radon,
+ double? Illuminance,
+ double? SoundLevel
+);
\ No newline at end of file
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/ActivateDevices.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/ActivateDevices.cs
similarity index 100%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/ActivateDevices.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/ActivateDevices.cs
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceActivated.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceActivated.cs
similarity index 62%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceActivated.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceActivated.cs
index 4b305a9..fbbba1c 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceActivated.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceActivated.cs
@@ -2,5 +2,5 @@
public class DeviceActivated
{
- public DefaultDeviceDTO Device { get; set; }
+ public DevicesMessage_DefaultDevice Device { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceCreated.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceCreated.cs
similarity index 61%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceCreated.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceCreated.cs
index 11a08a4..2f9e4cf 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceCreated.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceCreated.cs
@@ -2,5 +2,5 @@
public class DeviceCreated
{
- public DefaultDeviceDTO Device { get; set; }
+ public DevicesMessage_DefaultDevice Device { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceDeleted.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceDeleted.cs
similarity index 61%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceDeleted.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceDeleted.cs
index 71d94f3..af5bcc0 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceDeleted.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceDeleted.cs
@@ -2,5 +2,5 @@
public class DeviceDeleted
{
- public DefaultDeviceDTO Device { get; set; }
+ public DevicesMessage_DefaultDevice Device { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceGenerateMeasurement.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceGenerateMeasurement.cs
similarity index 84%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceGenerateMeasurement.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceGenerateMeasurement.cs
index 914b506..5fdc621 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceGenerateMeasurement.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceGenerateMeasurement.cs
@@ -10,5 +10,5 @@ public class DeviceGenerateMeasurement
///
/// Description of device that will generate measurement.
///
- public DefaultDeviceDTO Device { get; set; }
+ public DevicesMessage_DefaultDevice Device { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceStatusChanged.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceStatusChanged.cs
similarity index 63%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceStatusChanged.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceStatusChanged.cs
index 6ededc7..d7d074d 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceStatusChanged.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceStatusChanged.cs
@@ -2,5 +2,5 @@
public class DeviceStatusChanged
{
- public DefaultDeviceDTO Device { get; set; }
+ public DevicesMessage_DefaultDevice Device { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceUpdated.cs b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceUpdated.cs
similarity index 61%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceUpdated.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceUpdated.cs
index 1e4c417..cd36254 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Devices/DeviceUpdated.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Devices/DeviceUpdated.cs
@@ -2,5 +2,5 @@
public class DeviceUpdated
{
- public DefaultDeviceDTO Device { get; set; }
+ public DevicesMessage_DefaultDevice Device { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Measurements/CreateMeasurement.cs b/Common/CommonServiceLibrary.Messaging/Topics/Measurements/CreateMeasurement.cs
similarity index 59%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Measurements/CreateMeasurement.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Measurements/CreateMeasurement.cs
index c6cba52..325d967 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Measurements/CreateMeasurement.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Measurements/CreateMeasurement.cs
@@ -2,5 +2,5 @@
public class CreateMeasurement
{
- public CreateMeasurementDTO Measurement { get; set; }
+ public MeasurementsMessage_CreateMeasurement Measurement { get; set; }
}
diff --git a/Common/CommonServiceLibrary.Messaging/TopicMessages/Measurements/MeasurementCreated.cs b/Common/CommonServiceLibrary.Messaging/Topics/Measurements/MeasurementCreated.cs
similarity index 58%
rename from Common/CommonServiceLibrary.Messaging/TopicMessages/Measurements/MeasurementCreated.cs
rename to Common/CommonServiceLibrary.Messaging/Topics/Measurements/MeasurementCreated.cs
index 00cef7d..ce57e0c 100644
--- a/Common/CommonServiceLibrary.Messaging/TopicMessages/Measurements/MeasurementCreated.cs
+++ b/Common/CommonServiceLibrary.Messaging/Topics/Measurements/MeasurementCreated.cs
@@ -2,5 +2,5 @@
public class MeasurementCreated
{
- public DefaultMeasurementDTO Measurement { get; set; }
+ public MeasurementsMessage_DefaultMeasurement Measurement { get; set; }
}
diff --git a/Server.sln b/Server.sln
index b5cba12..4df6bf5 100644
--- a/Server.sln
+++ b/Server.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.14.36301.6
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.11222.15 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{2F6AB31D-A9B5-4E1E-A9A5-6A03F0BEFF2A}"
EndProject
@@ -39,8 +39,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raports.Domain", "Services\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raports.Infrastructure", "Services\Raports\Raports.Infrastructure\Raports.Infrastructure.csproj", "{81D82295-0F9A-496B-B626-4B28BC622ADB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonServiceLibrary.BlobStorage", "Common\CommonServiceLibrary.BlobStorage\CommonServiceLibrary.BlobStorage.csproj", "{EB4C49CC-4CA2-42CE-9F36-75D624DAD6E4}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonServiceLibrary.Messaging", "Common\CommonServiceLibrary.Messaging\CommonServiceLibrary.Messaging.csproj", "{D85F7E2F-9458-459C-908E-D331279E4A7B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raports.API", "Services\Raports\Raports.API\Raports.API.csproj", "{7ACCF4E0-4239-4AD7-A93B-41510ACCA016}"
@@ -129,10 +127,6 @@ Global
{81D82295-0F9A-496B-B626-4B28BC622ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81D82295-0F9A-496B-B626-4B28BC622ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81D82295-0F9A-496B-B626-4B28BC622ADB}.Release|Any CPU.Build.0 = Release|Any CPU
- {EB4C49CC-4CA2-42CE-9F36-75D624DAD6E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EB4C49CC-4CA2-42CE-9F36-75D624DAD6E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EB4C49CC-4CA2-42CE-9F36-75D624DAD6E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EB4C49CC-4CA2-42CE-9F36-75D624DAD6E4}.Release|Any CPU.Build.0 = Release|Any CPU
{D85F7E2F-9458-459C-908E-D331279E4A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D85F7E2F-9458-459C-908E-D331279E4A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D85F7E2F-9458-459C-908E-D331279E4A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -205,7 +199,6 @@ Global
{7283A0DE-206C-41B0-99CA-AADA99063374} = {2F6AB31D-A9B5-4E1E-A9A5-6A03F0BEFF2A}
{ACDCA4F1-5F1C-41E2-B96E-571B7B84C6FE} = {7283A0DE-206C-41B0-99CA-AADA99063374}
{81D82295-0F9A-496B-B626-4B28BC622ADB} = {7283A0DE-206C-41B0-99CA-AADA99063374}
- {EB4C49CC-4CA2-42CE-9F36-75D624DAD6E4} = {8211D36B-79BB-4E67-8717-458D1E8815EE}
{D85F7E2F-9458-459C-908E-D331279E4A7B} = {8211D36B-79BB-4E67-8717-458D1E8815EE}
{7ACCF4E0-4239-4AD7-A93B-41510ACCA016} = {7283A0DE-206C-41B0-99CA-AADA99063374}
{B682858B-1FBB-4089-8D21-0A59AE99533D} = {7283A0DE-206C-41B0-99CA-AADA99063374}
diff --git a/Services/Devices/Devices.Application/Consumers/ActivateDevicesConsumer.cs b/Services/Devices/Devices.Application/Consumers/ActivateDevicesConsumer.cs
index 3a51c54..64eade5 100644
--- a/Services/Devices/Devices.Application/Consumers/ActivateDevicesConsumer.cs
+++ b/Services/Devices/Devices.Application/Consumers/ActivateDevicesConsumer.cs
@@ -10,12 +10,12 @@ public async Task Consume(ConsumeContext context)
.Include(x => x.Timestamp)
.Include(x => x.Status)
.Include(x => x.Location)
- .Include(x=>x.MeasurementTypes)
+ .Include(x => x.MeasurementTypes)
.ToListAsync();
var devicesDtos = devices.Adapt>();
- var deviceActivatedMessages = devicesDtos.Select(x => new DeviceActivated() { Device = x });
+ var deviceActivatedMessages = devicesDtos.Select(x => new DeviceActivated() { Device = x.Adapt() });
await publisher.PublishBatch(deviceActivatedMessages);
}
diff --git a/Services/Devices/Devices.Application/DependencyInjection.cs b/Services/Devices/Devices.Application/DependencyInjection.cs
index e17ff26..0989e8f 100644
--- a/Services/Devices/Devices.Application/DependencyInjection.cs
+++ b/Services/Devices/Devices.Application/DependencyInjection.cs
@@ -4,53 +4,15 @@ public static class DependencyInjection
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment)
{
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID)
- .Map(x => x.Name, y => y.Name);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID)
- .Map(x => x.Name, y => y.Name);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID)
- .Map(x => x.Cron, y => y.Cron);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID)
- .Map(x => x.Cron, y => y.Cron);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.Name, y => y.Name)
- .Map(x => x.Unit, y => y.Unit)
- .Map(x => x.ID, y => y.ID);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.Name, y => y.Name)
- .Map(x => x.Unit, y => y.Unit)
- .Map(x => x.ID, y => y.ID);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ID, y => y.ID);
+ var mappings = new TypeAdapterConfig();
+
+ mappings.Apply(new DeviceMapper());
+ mappings.Apply(new LocationMapper());
+ mappings.Apply(new MeasurementTypeMapper());
+ mappings.Apply(new StatusMapper());
+ mappings.Apply(new TimestampMapper());
+
+ services.AddSingleton(mappings);
services.AddCarter();
diff --git a/Services/Devices/Devices.Application/Devices/CreateDevice/CreateDeviceHandler.cs b/Services/Devices/Devices.Application/Devices/CreateDevice/CreateDeviceHandler.cs
index 75d83e0..d158ec8 100644
--- a/Services/Devices/Devices.Application/Devices/CreateDevice/CreateDeviceHandler.cs
+++ b/Services/Devices/Devices.Application/Devices/CreateDevice/CreateDeviceHandler.cs
@@ -1,4 +1,6 @@
-namespace Devices.Application.Devices.CreateDevice;
+using CommonServiceLibrary.Messaging.Messages.DevicesService;
+
+namespace Devices.Application.Devices.CreateDevice;
public class CreateDeviceHandler(DevicesDBContext context, IPublishEndpoint publisher, IHubContext hubContext) : IRequestHandler
{
@@ -53,9 +55,11 @@ public async Task Handle(CreateDeviceCommand request, Cancell
}
var deviceDTO = newDevice.Adapt();
+ var messageDevice = newDevice.Adapt();
+
var message = new DeviceCreated()
{
- Device = deviceDTO,
+ Device = messageDevice,
};
await publisher.Publish(message, cancellationToken);
diff --git a/Services/Devices/Devices.Application/Devices/DeleteDevice/DeleteDeviceHandler.cs b/Services/Devices/Devices.Application/Devices/DeleteDevice/DeleteDeviceHandler.cs
index fd400c4..ec591e1 100644
--- a/Services/Devices/Devices.Application/Devices/DeleteDevice/DeleteDeviceHandler.cs
+++ b/Services/Devices/Devices.Application/Devices/DeleteDevice/DeleteDeviceHandler.cs
@@ -19,9 +19,11 @@ public async Task Handle(DeleteDeviceCommand request, Cancell
await context.SaveChangesAsync();
var dto = definedDevice.Adapt();
+ var messageDevice = definedDevice.Adapt();
+
var mqMessage = new DeviceDeleted()
{
- Device = dto,
+ Device = messageDevice,
};
await publisher.Publish(mqMessage, cancellationToken);
diff --git a/Services/Devices/Devices.Application/Devices/DeviceEndpoints.cs b/Services/Devices/Devices.Application/Devices/DeviceEndpoints.cs
index ba85bf3..27174ea 100644
--- a/Services/Devices/Devices.Application/Devices/DeviceEndpoints.cs
+++ b/Services/Devices/Devices.Application/Devices/DeviceEndpoints.cs
@@ -30,6 +30,14 @@ public void AddRoutes(IEndpointRouteBuilder app)
return Results.Ok(dtos);
});
+ app.MapGet("/devices/statuses/all", async (ISender sender) =>
+ {
+ var response = await sender.Send(new GetAllStatusesCommand());
+ var dtos = response.Statuses;
+
+ return Results.Ok(dtos);
+ });
+
app.MapGet("/devices/locations/all", async (ISender sender) =>
{
var response = await sender.Send(new GetAllLocationsComand());
diff --git a/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceCommands.cs b/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceCommands.cs
index be24a5b..b350733 100644
--- a/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceCommands.cs
+++ b/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceCommands.cs
@@ -65,5 +65,15 @@ public class GetAllMeasurementTypesCommandValidator : AbstractValidator Statuses);
+public record GetAllStatusesCommand() : IRequest;
+public class GetAllStatusesCommandValidator : AbstractValidator
+{
+ public GetAllStatusesCommandValidator()
+ {
+
}
}
\ No newline at end of file
diff --git a/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceHandlers.cs b/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceHandlers.cs
index b3b621b..77f3136 100644
--- a/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceHandlers.cs
+++ b/Services/Devices/Devices.Application/Devices/GetDevice/GetDeviceHandlers.cs
@@ -63,6 +63,18 @@ public async Task Handle(GetDeviceCommands request, Cance
}
}
+ public class GetAllStatusesHandler(DevicesDBContext context) : IRequestHandler
+ {
+ public async Task Handle(GetAllStatusesCommand request, CancellationToken cancellationToken)
+ {
+ var items = await context.Statuses.ToListAsync(cancellationToken);
+ var dtos = items.Adapt>();
+ var response = new GetAllStatusesResponse(dtos);
+
+ return response;
+ }
+ }
+
public class GetAllMeasurementTypesHandler(DevicesDBContext context) : IRequestHandler
{
public async Task Handle(GetAllMeasurementTypesCommand request, CancellationToken cancellationToken)
diff --git a/Services/Devices/Devices.Application/Devices/UpdateDevice/UpdateDeviceHandler.cs b/Services/Devices/Devices.Application/Devices/UpdateDevice/UpdateDeviceHandler.cs
index abb587e..28abd3e 100644
--- a/Services/Devices/Devices.Application/Devices/UpdateDevice/UpdateDeviceHandler.cs
+++ b/Services/Devices/Devices.Application/Devices/UpdateDevice/UpdateDeviceHandler.cs
@@ -32,9 +32,11 @@ public async Task Handle(UpdateDeviceStatusCommand request, C
await context.SaveChangesAsync();
var deviceDTO = foundDevice.Adapt();
+ var messageDevice = foundDevice.Adapt();
+
var message = new DeviceStatusChanged()
{
- Device = deviceDTO,
+ Device = messageDevice,
};
await publisher.Publish(message, cancellationToken);
@@ -93,9 +95,10 @@ public async Task Handle(UpdateDeviceMeasurementTypeCommand r
var dto = foundDevice.Adapt();
+ var messageDevice = foundDevice.Adapt();
var message = new DeviceUpdated()
{
- Device = dto,
+ Device = messageDevice,
};
await publisher.Publish(message, cancellationToken);
@@ -170,9 +173,10 @@ public async Task Handle(UpdateDeviceCommand request, Cancell
await context.SaveChangesAsync();
var dto = foundDevice.Adapt();
+ var messageDevice = foundDevice.Adapt();
var message = new DeviceUpdated()
{
- Device = dto,
+ Device = messageDevice,
};
await publisher.Publish(message, cancellationToken);
diff --git a/Services/Devices/Devices.Application/GlobalUsings.cs b/Services/Devices/Devices.Application/GlobalUsings.cs
index be9b0c0..e40098b 100644
--- a/Services/Devices/Devices.Application/GlobalUsings.cs
+++ b/Services/Devices/Devices.Application/GlobalUsings.cs
@@ -2,6 +2,7 @@
global using CommonServiceLibrary.Behaviors;
global using CommonServiceLibrary.Exceptions;
global using CommonServiceLibrary.Exceptions.Handlers;
+global using CommonServiceLibrary.Messaging.Messages.DevicesService;
global using CommonServiceLibrary.Messaging.TopicMessages.Devices;
global using Devices.Application.Consumers;
global using Devices.Application.Devices.CreateDevice;
@@ -10,6 +11,7 @@
global using Devices.Application.Devices.UpdateDevice;
global using Devices.Application.Exceptions;
global using Devices.Application.Hubs;
+global using Devices.Application.Mappers;
global using Devices.DataTransferObjects;
global using Devices.Domain.Models;
global using Devices.Infrastructure.Database;
diff --git a/Services/Devices/Devices.Application/Mappers/DeviceMapper.cs b/Services/Devices/Devices.Application/Mappers/DeviceMapper.cs
new file mode 100644
index 0000000..aef34b2
--- /dev/null
+++ b/Services/Devices/Devices.Application/Mappers/DeviceMapper.cs
@@ -0,0 +1,45 @@
+namespace Devices.Application.Mappers;
+
+internal class DeviceMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.RegisterDate, y => y.RegisterDate)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.RegisterDate, y => y.RegisterDate)
+ .Map(x => x.Name, y => y.Name);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.RegisterDate, y => y.RegisterDate)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.RegisterDate, y => y.RegisterDate)
+ .Map(x => x.Name, y => y.Name);
+
+ // --------------- Mappings with dto types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID);
+ }
+}
diff --git a/Services/Devices/Devices.Application/Mappers/LocationMapper.cs b/Services/Devices/Devices.Application/Mappers/LocationMapper.cs
new file mode 100644
index 0000000..f8e751b
--- /dev/null
+++ b/Services/Devices/Devices.Application/Mappers/LocationMapper.cs
@@ -0,0 +1,45 @@
+namespace Devices.Application.Mappers;
+
+internal class LocationMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Hash, y => y.Hash)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Hash, y => y.Hash)
+ .Map(x => x.Name, y => y.Name);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Hash, y => y.Hash)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Hash, y => y.Hash)
+ .Map(x => x.Name, y => y.Name);
+
+ // --------------- Mappings with dtos types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Hash, y => y.Hash)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Hash, y => y.Hash)
+ .Map(x => x.Name, y => y.Name);
+ }
+}
diff --git a/Services/Devices/Devices.Application/Mappers/MeasurementTypeMapper.cs b/Services/Devices/Devices.Application/Mappers/MeasurementTypeMapper.cs
new file mode 100644
index 0000000..ba2407b
--- /dev/null
+++ b/Services/Devices/Devices.Application/Mappers/MeasurementTypeMapper.cs
@@ -0,0 +1,45 @@
+namespace Devices.Application.Mappers;
+
+internal class MeasurementTypeMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Unit, y => y.Unit)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Unit, y => y.Unit)
+ .Map(x => x.Name, y => y.Name);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Unit, y => y.Unit)
+ .Map(x => x.Name, y => y.Name);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Unit, y => y.Unit)
+ .Map(x => x.Name, y => y.Name);
+
+ // --------------- Mappings with dtos types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.Name, y => y.Name)
+ .Map(x => x.Unit, y => y.Unit)
+ .Map(x => x.ID, y => y.ID);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.Name, y => y.Name)
+ .Map(x => x.Unit, y => y.Unit)
+ .Map(x => x.ID, y => y.ID);
+ }
+}
diff --git a/Services/Devices/Devices.Application/Mappers/StatusMapper.cs b/Services/Devices/Devices.Application/Mappers/StatusMapper.cs
new file mode 100644
index 0000000..c07e132
--- /dev/null
+++ b/Services/Devices/Devices.Application/Mappers/StatusMapper.cs
@@ -0,0 +1,37 @@
+namespace Devices.Application.Mappers;
+
+internal class StatusMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Type, y => y.Type);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Type, y => y.Type);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Type, y => y.Type);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Type, y => y.Type);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID);
+ }
+}
diff --git a/Services/Devices/Devices.Application/Mappers/TimestampMapper.cs b/Services/Devices/Devices.Application/Mappers/TimestampMapper.cs
new file mode 100644
index 0000000..42053bc
--- /dev/null
+++ b/Services/Devices/Devices.Application/Mappers/TimestampMapper.cs
@@ -0,0 +1,39 @@
+namespace Devices.Application.Mappers;
+
+internal class TimestampMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Cron, y => y.Cron);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Cron, y => y.Cron);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Cron, y => y.Cron);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Cron, y => y.Cron);
+
+ // --------------- Mappings with dtos types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Cron, y => y.Cron);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.Cron, y => y.Cron);
+ }
+}
diff --git a/Services/Devices/Devices.DataTransferObjects/LocationDTO.cs b/Services/Devices/Devices.DataTransferObjects/LocationDTO.cs
index 5704499..b8cbe77 100644
--- a/Services/Devices/Devices.DataTransferObjects/LocationDTO.cs
+++ b/Services/Devices/Devices.DataTransferObjects/LocationDTO.cs
@@ -1,3 +1,3 @@
namespace Devices.DataTransferObjects;
-public record LocationDTO(int ID, string Name);
\ No newline at end of file
+public record LocationDTO(int ID, string Name, Guid Hash);
\ No newline at end of file
diff --git a/Services/Devices/Devices.Domain/Models/Location.cs b/Services/Devices/Devices.Domain/Models/Location.cs
index 9f8e81c..b821969 100644
--- a/Services/Devices/Devices.Domain/Models/Location.cs
+++ b/Services/Devices/Devices.Domain/Models/Location.cs
@@ -3,6 +3,6 @@
public class Location
{
public int ID { get; set; }
-
public string Name { get; set; }
+ public Guid Hash { get; set; }
}
\ No newline at end of file
diff --git a/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Protos/devices.proto b/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Protos/devices.proto
index b798f31..442f900 100644
--- a/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Protos/devices.proto
+++ b/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Protos/devices.proto
@@ -8,6 +8,11 @@ service DevicesService {
rpc GetDeviceByDeviceNumber (DeviceRequest) returns (GrpcDeviceModel);
rpc GetAllDevices (DeviceAllRequest) returns (stream GrpcDeviceModel);
rpc GetAllLocations (LocationAllRequest) returns (stream GrpcLocationModel);
+ rpc GetLocationByName (LocationByNameRequest) returns (GrpcLocationModel);
+}
+
+message LocationByNameRequest{
+ string name = 1;
}
message LocationAllRequest{
@@ -25,6 +30,7 @@ message DeviceRequest{
message GrpcLocationModel {
int32 id = 1;
string name = 2;
+ string hash = 3;
}
message GrpcTimestampConfigurationModel {
diff --git a/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Services/DevicesServerService.cs b/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Services/DevicesServerService.cs
index 85961ab..045fbbd 100644
--- a/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Services/DevicesServerService.cs
+++ b/Services/Devices/Devices.GRPCServer/Devices.GRPCServer/Services/DevicesServerService.cs
@@ -2,6 +2,19 @@
public class DevicesServerService(ILogger logger, DevicesDBContext dbcontext) : DevicesService.DevicesServiceBase
{
+ public override async Task GetLocationByName(LocationByNameRequest request, ServerCallContext context)
+ {
+ var locationDB = await dbcontext.Locations.FirstOrDefaultAsync(x => x.Name == request.Name);
+ if (locationDB is null)
+ {
+ throw new Exception();
+ }
+
+ var deviceGRPC = locationDB.Adapt();
+
+ return deviceGRPC;
+ }
+
public override async Task GetAllLocations(LocationAllRequest request, IServerStreamWriter responseStream, ServerCallContext context)
{
var locations = await dbcontext.Locations.ToListAsync();
diff --git a/Services/Devices/Devices.Infrastructure/Configurations/LocationEntityConfiguration.cs b/Services/Devices/Devices.Infrastructure/Configurations/LocationEntityConfiguration.cs
index 7faaeb8..5330c4b 100644
--- a/Services/Devices/Devices.Infrastructure/Configurations/LocationEntityConfiguration.cs
+++ b/Services/Devices/Devices.Infrastructure/Configurations/LocationEntityConfiguration.cs
@@ -7,5 +7,6 @@ public void Configure(EntityTypeBuilder builder)
builder.HasKey(x => x.ID);
builder.Property(b => b.ID).ValueGeneratedOnAdd();
builder.Property(x => x.Name).HasComment("Name of location, for example: 'Kitchen'...").IsRequired();
+ builder.Property(x => x.Hash).IsRequired();
}
}
diff --git a/Services/Devices/Devices.Infrastructure/Migrations/20251129140540_2-Hash-Property-Added-To-Location-Entity.Designer.cs b/Services/Devices/Devices.Infrastructure/Migrations/20251129140540_2-Hash-Property-Added-To-Location-Entity.Designer.cs
new file mode 100644
index 0000000..2a857f6
--- /dev/null
+++ b/Services/Devices/Devices.Infrastructure/Migrations/20251129140540_2-Hash-Property-Added-To-Location-Entity.Designer.cs
@@ -0,0 +1,220 @@
+//
+using System;
+using Devices.Infrastructure.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Devices.Infrastructure.Migrations
+{
+ [DbContext(typeof(DevicesDBContext))]
+ [Migration("20251129140540_2-Hash-Property-Added-To-Location-Entity")]
+ partial class _2HashPropertyAddedToLocationEntity
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.7")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Devices.Domain.Models.Device", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("DeviceNumber")
+ .HasColumnType("uniqueidentifier")
+ .HasComment("This is unique name for device");
+
+ b.Property("LocationID")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RegisterDate")
+ .HasColumnType("datetime2");
+
+ b.Property("StatusID")
+ .HasColumnType("int");
+
+ b.Property("TimestampID")
+ .HasColumnType("int");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeviceNumber")
+ .IsUnique();
+
+ b.HasIndex("LocationID");
+
+ b.HasIndex("StatusID");
+
+ b.HasIndex("TimestampID");
+
+ b.ToTable("Devices");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.DeviceMeasurementType", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("DeviceID")
+ .HasColumnType("int");
+
+ b.Property("MeasurementTypeID")
+ .HasColumnType("int");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeviceID");
+
+ b.HasIndex("MeasurementTypeID");
+
+ b.ToTable("DeviceMeasurementTypes");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.Location", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("Hash")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasComment("Name of location, for example: 'Kitchen'...");
+
+ b.HasKey("ID");
+
+ b.ToTable("Locations");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.MeasurementType", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasComment("For example: 'Air Temperature'");
+
+ b.Property("Unit")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasComment("For example: 'ppm'");
+
+ b.HasKey("ID");
+
+ b.ToTable("MeasurementTypes");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.Status", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("ID");
+
+ b.ToTable("Statuses");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.Timestamp", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("Cron")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasComment("Timestamp measurement configuration stored in CRON format");
+
+ b.HasKey("ID");
+
+ b.ToTable("Timestamps");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.Device", b =>
+ {
+ b.HasOne("Devices.Domain.Models.Location", "Location")
+ .WithMany()
+ .HasForeignKey("LocationID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Devices.Domain.Models.Status", "Status")
+ .WithMany()
+ .HasForeignKey("StatusID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Devices.Domain.Models.Timestamp", "Timestamp")
+ .WithMany()
+ .HasForeignKey("TimestampID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Location");
+
+ b.Navigation("Status");
+
+ b.Navigation("Timestamp");
+ });
+
+ modelBuilder.Entity("Devices.Domain.Models.DeviceMeasurementType", b =>
+ {
+ b.HasOne("Devices.Domain.Models.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Devices.Domain.Models.MeasurementType", "MeasurementType")
+ .WithMany()
+ .HasForeignKey("MeasurementTypeID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Device");
+
+ b.Navigation("MeasurementType");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Services/Devices/Devices.Infrastructure/Migrations/20251129140540_2-Hash-Property-Added-To-Location-Entity.cs b/Services/Devices/Devices.Infrastructure/Migrations/20251129140540_2-Hash-Property-Added-To-Location-Entity.cs
new file mode 100644
index 0000000..2b2483e
--- /dev/null
+++ b/Services/Devices/Devices.Infrastructure/Migrations/20251129140540_2-Hash-Property-Added-To-Location-Entity.cs
@@ -0,0 +1,30 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Devices.Infrastructure.Migrations
+{
+ ///
+ public partial class _2HashPropertyAddedToLocationEntity : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "Hash",
+ table: "Locations",
+ type: "uniqueidentifier",
+ nullable: false,
+ defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "Hash",
+ table: "Locations");
+ }
+ }
+}
diff --git a/Services/Devices/Devices.Infrastructure/Migrations/DevicesDBContextModelSnapshot.cs b/Services/Devices/Devices.Infrastructure/Migrations/DevicesDBContextModelSnapshot.cs
index 97192eb..7bfc06f 100644
--- a/Services/Devices/Devices.Infrastructure/Migrations/DevicesDBContextModelSnapshot.cs
+++ b/Services/Devices/Devices.Infrastructure/Migrations/DevicesDBContextModelSnapshot.cs
@@ -95,6 +95,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+ b.Property("Hash")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Name")
.IsRequired()
.HasColumnType("nvarchar(max)")
diff --git a/Services/Devices/Devices.Infrastructure/Scripts/001S_Initial-migration.sql b/Services/Devices/Devices.Infrastructure/Scripts/001S_Initial-migration.sql
index 1042221..b7d5a23 100644
--- a/Services/Devices/Devices.Infrastructure/Scripts/001S_Initial-migration.sql
+++ b/Services/Devices/Devices.Infrastructure/Scripts/001S_Initial-migration.sql
@@ -6,22 +6,22 @@ delete from [MeasurementTypes];
delete from [Statuses];
delete from [DeviceMeasurementTypes];
--- Create 'Locations' seed
-INSERT INTO Locations (Name)
+-- Create 'Locations' seed with explicit Hash values
+INSERT INTO Locations (Name, Hash)
VALUES
-('Kitchen'),
-('Living Room'),
-('Bedroom'),
-('Bathroom'),
-('Garage'),
-('Dining Room'),
-('Basement'),
-('Attic'),
-('Laundry Room'),
-('Office'),
-('Pantry'),
-('Storage Room'),
-('Guest Room');
+('Kitchen', '138DFE8E-F8C6-462D-A768-0B7910BA61B0'),
+('Living Room', '4E4D3F6C-88E0-49B4-A1EC-6F56820F3E2B'),
+('Bedroom', 'B7A3E5AF-0CE9-4A77-9BB4-2F8B35F707C9'),
+('Bathroom', '9D9B7487-3180-46D8-BB63-9A3E9EEE31C2'),
+('Garage', '5C1A06C8-0C78-4C56-9A27-6ADB0D59C2C1'),
+('Dining Room', '6B8E51E9-B20D-4FBE-8F52-2F1E187C3E2F'),
+('Basement', 'E03A04F0-8DBF-41E8-9C01-A07F9245DA19'),
+('Attic', '0A9EA1ED-6C2C-4AC8-9D7F-83EDE7A45C67'),
+('Laundry Room', '81B3176C-01C3-4C2B-8E3E-4E4C95514637'),
+('Office', 'F8B22A79-4127-48D7-9279-90D6CAC5300E'),
+('Pantry', 'D2CF6F18-9FEB-49CA-B0C8-DF4F777269B7'),
+('Storage Room', '3E5F3B79-1C7E-4AC8-8AF0-53266F9073D7'),
+('Guest Room', 'B6E15A99-0A22-4512-A604-6D5B5C553F27');
DECLARE @loc1 INT, @loc2 INT, @loc3 INT, @loc4 INT, @loc5 INT,
@loc6 INT, @loc7 INT, @loc8 INT, @loc9 INT, @loc10 INT,
diff --git a/Services/Emulators/Emulators.Application/Consumers/DeviceGenerateMeasurementConsumer.cs b/Services/Emulators/Emulators.Application/Consumers/DeviceGenerateMeasurementConsumer.cs
index e0b32d3..90865bf 100644
--- a/Services/Emulators/Emulators.Application/Consumers/DeviceGenerateMeasurementConsumer.cs
+++ b/Services/Emulators/Emulators.Application/Consumers/DeviceGenerateMeasurementConsumer.cs
@@ -1,4 +1,6 @@
-namespace Emulators.Application.Consumers;
+using CommonServiceLibrary.Messaging.Messages.MeasurementsService;
+
+namespace Emulators.Application.Consumers;
internal class DeviceGenerateMeasurementConsumer(ILogger logger, EmulatorsDBContext database, IMemoryCache cashe, IPublishEndpoint publisher) : IConsumer
{
@@ -177,13 +179,13 @@ public async Task Consume(ConsumeContext context)
airTemperature = deviceSpreadedValue;
break;
case "Relative Humidity":
- relativeHumidity = (int)deviceSpreadedValue;
+ relativeHumidity = deviceSpreadedValue;
break;
case "Carbon Dioxide":
- carbonDioxide = (int)deviceSpreadedValue;
+ carbonDioxide = deviceSpreadedValue;
break;
case "Volatile Organic Compounds":
- voc = (int)deviceSpreadedValue;
+ voc = deviceSpreadedValue;
break;
case "Particulate Matter 1um":
pm1 = deviceSpreadedValue;
@@ -195,19 +197,19 @@ public async Task Consume(ConsumeContext context)
pm10 = deviceSpreadedValue;
break;
case "Formaldehyde":
- formaldehyde = (int)deviceSpreadedValue;
+ formaldehyde = deviceSpreadedValue;
break;
case "Carbon Monoxide":
carbonMonixide = deviceSpreadedValue;
break;
case "Ozone":
- ozone = (int)deviceSpreadedValue;
+ ozone = deviceSpreadedValue;
break;
case "Ammonia":
ammonia = deviceSpreadedValue;
break;
case "Air Flow Rate":
- airFlowRate = (int)deviceSpreadedValue;
+ airFlowRate = deviceSpreadedValue;
break;
case "Air Ionization Level":
airIonizationLevel = deviceSpreadedValue;
@@ -216,13 +218,13 @@ public async Task Consume(ConsumeContext context)
oxygen = deviceSpreadedValue;
break;
case "Radon Concentration":
- radon = (int)deviceSpreadedValue;
+ radon = deviceSpreadedValue;
break;
case "Illuminance level":
- illuminance = (int)deviceSpreadedValue;
+ illuminance = deviceSpreadedValue;
break;
case "Sound Pressure Level":
- sound = (int)deviceSpreadedValue;
+ sound = deviceSpreadedValue;
break;
default:
logger.LogError($"Unhandled assingment of generated value for '{chart.Measurement.Name}'");
@@ -234,7 +236,7 @@ public async Task Consume(ConsumeContext context)
var creationDate = dateArg.AddMinutes(sw.ElapsedMilliseconds);
- var createMeasurement = new CreateMeasurementDTO(deviceArg.DeviceNumber, deviceArg.Location.ID, creationDate, airTemperature, relativeHumidity,
+ var createMeasurement = new MeasurementsMessage_CreateMeasurement(Guid.NewGuid(), deviceArg.DeviceNumber, creationDate, deviceArg.Location.Hash, airTemperature, relativeHumidity,
carbonDioxide, voc, pm1, pm25, pm10, formaldehyde, carbonDioxide, ozone, ammonia, airFlowRate, airIonizationLevel, oxygen, radon, illuminance, sound);
var topicMessage = new CreateMeasurement()
diff --git a/Services/Emulators/Emulators.Application/DependencyInjection.cs b/Services/Emulators/Emulators.Application/DependencyInjection.cs
index c2e8c2f..ecb39f7 100644
--- a/Services/Emulators/Emulators.Application/DependencyInjection.cs
+++ b/Services/Emulators/Emulators.Application/DependencyInjection.cs
@@ -4,6 +4,11 @@ public static class DependencyInjection
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment)
{
+ services.Configure(options =>
+ {
+ options.SerializerOptions.DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull;
+ });
+
services.AddMemoryCache();
services.AddQuartz(options =>
diff --git a/Services/Emulators/Emulators.Application/GlobalUsings.cs b/Services/Emulators/Emulators.Application/GlobalUsings.cs
index 6e012a1..ca3c3a3 100644
--- a/Services/Emulators/Emulators.Application/GlobalUsings.cs
+++ b/Services/Emulators/Emulators.Application/GlobalUsings.cs
@@ -1,14 +1,14 @@
-global using CommonServiceLibrary.Messaging.TopicMessages.Devices;
+global using CommonServiceLibrary.Messaging.Messages.DevicesService;
+global using CommonServiceLibrary.Messaging.TopicMessages.Devices;
global using CommonServiceLibrary.Messaging.TopicMessages.Measurements;
-global using Devices.DataTransferObjects;
global using Emulators.Application.Consumers;
global using Emulators.Application.Jobs;
global using Emulators.Application.Publishers;
global using Emulators.Domain.Models;
global using Emulators.Infrastructure.Database;
global using MassTransit;
-global using Measurements.DataTransferObjects;
global using Microsoft.AspNetCore.Hosting;
+global using Microsoft.AspNetCore.Http.Json;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Caching.Memory;
global using Microsoft.Extensions.Configuration;
diff --git a/Services/Emulators/Emulators.Application/Jobs/EnqueueMeasurementGenerationJob.cs b/Services/Emulators/Emulators.Application/Jobs/EnqueueMeasurementGenerationJob.cs
index 46ca74d..64cefe2 100644
--- a/Services/Emulators/Emulators.Application/Jobs/EnqueueMeasurementGenerationJob.cs
+++ b/Services/Emulators/Emulators.Application/Jobs/EnqueueMeasurementGenerationJob.cs
@@ -8,7 +8,7 @@ public async Task Execute(IJobExecutionContext context)
{
var jobDataArg = context.MergedJobDataMap["Device"];
- if (jobDataArg is not DefaultDeviceDTO jobDeviceArgument)
+ if (jobDataArg is not DevicesMessage_DefaultDevice jobDeviceArgument)
{
logger.LogError($"{nameof(EnqueueMeasurementGenerationJob)} - Failed to parse input arguments!");
return;
diff --git a/Services/Emulators/Emulators.Domain/Models/Location.cs b/Services/Emulators/Emulators.Domain/Models/Location.cs
index 8fb2400..708c2d8 100644
--- a/Services/Emulators/Emulators.Domain/Models/Location.cs
+++ b/Services/Emulators/Emulators.Domain/Models/Location.cs
@@ -4,4 +4,5 @@ public class Location
{
public int ID { get; set; }
public string Name { get; set; }
+ public Guid Hash { get; set; }
}
diff --git a/Services/Emulators/Emulators.Infrastructure/Configurations/LocationEntityConfiguration.cs b/Services/Emulators/Emulators.Infrastructure/Configurations/LocationEntityConfiguration.cs
index 0df70fb..20feaaf 100644
--- a/Services/Emulators/Emulators.Infrastructure/Configurations/LocationEntityConfiguration.cs
+++ b/Services/Emulators/Emulators.Infrastructure/Configurations/LocationEntityConfiguration.cs
@@ -8,5 +8,6 @@ public void Configure(EntityTypeBuilder builder)
builder.Property(b => b.ID).ValueGeneratedOnAdd();
builder.Property(x => x.Name).IsRequired();
builder.HasIndex(x => x.Name).IsUnique();
+ builder.Property(x => x.Hash).IsRequired();
}
}
diff --git a/Services/Emulators/Emulators.Infrastructure/Migrations/20251129141216_2-Hash-Property-Added-To-Location-Entity.Designer.cs b/Services/Emulators/Emulators.Infrastructure/Migrations/20251129141216_2-Hash-Property-Added-To-Location-Entity.Designer.cs
new file mode 100644
index 0000000..a77a02d
--- /dev/null
+++ b/Services/Emulators/Emulators.Infrastructure/Migrations/20251129141216_2-Hash-Property-Added-To-Location-Entity.Designer.cs
@@ -0,0 +1,220 @@
+//
+using System;
+using Emulators.Infrastructure.Database;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Emulators.Infrastructure.Migrations
+{
+ [DbContext(typeof(EmulatorsDBContext))]
+ [Migration("20251129141216_2-Hash-Property-Added-To-Location-Entity")]
+ partial class _2HashPropertyAddedToLocationEntity
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Emulators.Domain.Models.ChartOffset", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("ChartTemplateID")
+ .HasColumnType("int");
+
+ b.Property("LocationID")
+ .HasColumnType("int");
+
+ b.Property("Time")
+ .HasColumnType("int");
+
+ b.Property("Value")
+ .HasColumnType("float");
+
+ b.HasKey("ID");
+
+ b.HasIndex("ChartTemplateID");
+
+ b.HasIndex("LocationID");
+
+ b.ToTable("ChartOffsets");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.ChartTemplate", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("MeasurementID")
+ .HasColumnType("int");
+
+ b.HasKey("ID");
+
+ b.HasIndex("MeasurementID");
+
+ b.ToTable("ChartTemplates");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.Device", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("DeviceNumber")
+ .HasColumnType("uniqueidentifier")
+ .HasComment("This is unique name for device");
+
+ b.Property("Spread")
+ .HasColumnType("float")
+ .HasComment("Measurement value spread, expressed in percentage.");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeviceNumber")
+ .IsUnique();
+
+ b.ToTable("Devices", t =>
+ {
+ t.HasCheckConstraint("CK_Device_Spread_Range", "Spread >= 0 AND Spread <= 100");
+ });
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.Location", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("Hash")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("Locations");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.Measurement", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasComment("Name of measurement, for example 'Air Temperature'.");
+
+ b.Property("Unit")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)")
+ .HasComment("Unit");
+
+ b.HasKey("ID");
+
+ b.ToTable("Measurements");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.Sample", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+
+ b.Property("ChartTemplateID")
+ .HasColumnType("int");
+
+ b.Property("Time")
+ .HasColumnType("time");
+
+ b.Property("Value")
+ .HasColumnType("float");
+
+ b.HasKey("ID");
+
+ b.HasIndex("ChartTemplateID");
+
+ b.ToTable("Samples");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.ChartOffset", b =>
+ {
+ b.HasOne("Emulators.Domain.Models.ChartTemplate", "ChartTemplate")
+ .WithMany()
+ .HasForeignKey("ChartTemplateID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Emulators.Domain.Models.Location", "Location")
+ .WithMany()
+ .HasForeignKey("LocationID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ChartTemplate");
+
+ b.Navigation("Location");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.ChartTemplate", b =>
+ {
+ b.HasOne("Emulators.Domain.Models.Measurement", "Measurement")
+ .WithMany()
+ .HasForeignKey("MeasurementID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Measurement");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.Sample", b =>
+ {
+ b.HasOne("Emulators.Domain.Models.ChartTemplate", "ChartTemplate")
+ .WithMany("Samples")
+ .HasForeignKey("ChartTemplateID")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ChartTemplate");
+ });
+
+ modelBuilder.Entity("Emulators.Domain.Models.ChartTemplate", b =>
+ {
+ b.Navigation("Samples");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Services/Emulators/Emulators.Infrastructure/Migrations/20251129141216_2-Hash-Property-Added-To-Location-Entity.cs b/Services/Emulators/Emulators.Infrastructure/Migrations/20251129141216_2-Hash-Property-Added-To-Location-Entity.cs
new file mode 100644
index 0000000..ebb6a5a
--- /dev/null
+++ b/Services/Emulators/Emulators.Infrastructure/Migrations/20251129141216_2-Hash-Property-Added-To-Location-Entity.cs
@@ -0,0 +1,30 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Emulators.Infrastructure.Migrations
+{
+ ///
+ public partial class _2HashPropertyAddedToLocationEntity : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "Hash",
+ table: "Locations",
+ type: "uniqueidentifier",
+ nullable: false,
+ defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "Hash",
+ table: "Locations");
+ }
+ }
+}
diff --git a/Services/Emulators/Emulators.Infrastructure/Migrations/EmulatorsDBContextModelSnapshot.cs b/Services/Emulators/Emulators.Infrastructure/Migrations/EmulatorsDBContextModelSnapshot.cs
index 3c055e9..3e7a593 100644
--- a/Services/Emulators/Emulators.Infrastructure/Migrations/EmulatorsDBContextModelSnapshot.cs
+++ b/Services/Emulators/Emulators.Infrastructure/Migrations/EmulatorsDBContextModelSnapshot.cs
@@ -104,6 +104,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"));
+ b.Property("Hash")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Name")
.IsRequired()
.HasColumnType("nvarchar(450)");
diff --git a/Services/Emulators/Emulators.Infrastructure/Scripts/001S_Initial-migration.sql b/Services/Emulators/Emulators.Infrastructure/Scripts/001S_Initial-migration.sql
index e714409..c74018d 100644
--- a/Services/Emulators/Emulators.Infrastructure/Scripts/001S_Initial-migration.sql
+++ b/Services/Emulators/Emulators.Infrastructure/Scripts/001S_Initial-migration.sql
@@ -7,21 +7,21 @@ delete from [ChartOffsets];
delete from [Samples];
-- Create 'Locations' seed
-INSERT INTO Locations (Name)
+INSERT INTO Locations (Name, Hash)
VALUES
-('Kitchen'),
-('Living Room'),
-('Bedroom'),
-('Bathroom'),
-('Garage'),
-('Dining Room'),
-('Basement'),
-('Attic'),
-('Laundry Room'),
-('Office'),
-('Pantry'),
-('Storage Room'),
-('Guest Room');
+('Kitchen', '138DFE8E-F8C6-462D-A768-0B7910BA61B0'),
+('Living Room', '4E4D3F6C-88E0-49B4-A1EC-6F56820F3E2B'),
+('Bedroom', 'B7A3E5AF-0CE9-4A77-9BB4-2F8B35F707C9'),
+('Bathroom', '9D9B7487-3180-46D8-BB63-9A3E9EEE31C2'),
+('Garage', '5C1A06C8-0C78-4C56-9A27-6ADB0D59C2C1'),
+('Dining Room', '6B8E51E9-B20D-4FBE-8F52-2F1E187C3E2F'),
+('Basement', 'E03A04F0-8DBF-41E8-9C01-A07F9245DA19'),
+('Attic', '0A9EA1ED-6C2C-4AC8-9D7F-83EDE7A45C67'),
+('Laundry Room', '81B3176C-01C3-4C2B-8E3E-4E4C95514637'),
+('Office', 'F8B22A79-4127-48D7-9279-90D6CAC5300E'),
+('Pantry', 'D2CF6F18-9FEB-49CA-B0C8-DF4F777269B7'),
+('Storage Room', '3E5F3B79-1C7E-4AC8-8AF0-53266F9073D7'),
+('Guest Room', 'B6E15A99-0A22-4512-A604-6D5B5C553F27');
-- Create 'Devices' seed
insert into Devices (DeviceNumber, Spread)
diff --git a/Services/Measurements/Measurements.Application/Consumers/CreateMeasurementConsumer.cs b/Services/Measurements/Measurements.Application/Consumers/CreateMeasurementConsumer.cs
index 09693d2..4fdd34d 100644
--- a/Services/Measurements/Measurements.Application/Consumers/CreateMeasurementConsumer.cs
+++ b/Services/Measurements/Measurements.Application/Consumers/CreateMeasurementConsumer.cs
@@ -1,11 +1,27 @@
namespace Measurements.Application.Consumers;
-internal class CreateMeasurementConsumer(ILogger logger) : IConsumer
+internal class CreateMeasurementConsumer(ILogger logger, Container database, IPublishEndpoint massTransitPublisher, IHubContext signalRHub) : IConsumer
{
public async Task Consume(ConsumeContext context)
{
var createMeasurementDTO = context.Message.Measurement;
logger.LogInformation($"{createMeasurementDTO}");
+
+ var response = await database.CreateItemAsync(createMeasurementDTO);
+ if (response is null)
+ {
+ throw new Exception();
+ }
+
+ var measurementDto = response.Resource.Adapt();
+ var messageMeasurement = response.Resource.Adapt();
+ var message = new MeasurementCreated()
+ {
+ Measurement = messageMeasurement,
+ };
+
+ await massTransitPublisher.Publish(message);
+ await signalRHub.Clients.All.SendAsync("MeasurementCreated", measurementDto);
}
}
\ No newline at end of file
diff --git a/Services/Measurements/Measurements.Application/DependencyInjection.cs b/Services/Measurements/Measurements.Application/DependencyInjection.cs
index 2d06f84..a8441e1 100644
--- a/Services/Measurements/Measurements.Application/DependencyInjection.cs
+++ b/Services/Measurements/Measurements.Application/DependencyInjection.cs
@@ -1,20 +1,17 @@
-namespace Measurements.Application;
+using CommonServiceLibrary.GRPC;
+using Measurements.Application.Mappers;
+
+namespace Measurements.Application;
public static class DependencyInjection
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment)
{
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
- .Map(x => x.LocationID, y => y.LocationID)
- .Map(x => x.ID, y => y.ID);
-
- TypeAdapterConfig
- .NewConfig()
- .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
- .Map(x => x.LocationID, y => y.LocationID)
- .Map(x => x.ID, y => y.ID);
+ var mappings = new TypeAdapterConfig();
+
+ mappings.Apply(new MeasurementMapper());
+
+ services.AddSingleton(mappings);
services.AddCarter();
@@ -25,9 +22,24 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
x.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
+ services.AddGRPCMappings();
services.AddGrpcClient(options =>
{
- options.Address = new Uri(configuration.GetConnectionString("DevicesGRPC"));
+ string grpcConnectionString = string.Empty;
+ if (environment.IsDevelopment())
+ {
+ grpcConnectionString = configuration.GetConnectionString("DevicesGRPC_Dev");
+ }
+ else if (environment.IsStaging())
+ {
+ grpcConnectionString = configuration.GetConnectionString("DevicesGRPC_Dev");
+ }
+ else
+ {
+ grpcConnectionString = configuration.GetConnectionString("DevicesGRPC_Prod");
+ }
+
+ options.Address = new Uri(grpcConnectionString);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
diff --git a/Services/Measurements/Measurements.Application/GlobalUsings.cs b/Services/Measurements/Measurements.Application/GlobalUsings.cs
index afde5b8..ee08772 100644
--- a/Services/Measurements/Measurements.Application/GlobalUsings.cs
+++ b/Services/Measurements/Measurements.Application/GlobalUsings.cs
@@ -3,7 +3,7 @@
global using CommonServiceLibrary.DataStructures;
global using CommonServiceLibrary.Exceptions;
global using CommonServiceLibrary.Exceptions.Handlers;
-global using CommonServiceLibrary.Messaging.TopicMessages.Devices;
+global using CommonServiceLibrary.Messaging.Messages.MeasurementsService;
global using CommonServiceLibrary.Messaging.TopicMessages.Measurements;
global using Devices.GRPCClient;
global using FluentValidation;
@@ -27,5 +27,4 @@
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
-global using System.Reflection;
-
+global using System.Reflection;
\ No newline at end of file
diff --git a/Services/Measurements/Measurements.Application/Mappers/MeasurementMapper.cs b/Services/Measurements/Measurements.Application/Mappers/MeasurementMapper.cs
new file mode 100644
index 0000000..4408ca1
--- /dev/null
+++ b/Services/Measurements/Measurements.Application/Mappers/MeasurementMapper.cs
@@ -0,0 +1,153 @@
+namespace Measurements.Application.Mappers;
+
+internal class MeasurementMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.CarbonDioxide)
+ .Map(x => x.VolatileOrganicCompounds, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.CarbonMonoxide)
+ .Map(x => x.Ozone, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.CarbonDioxide)
+ .Map(x => x.VolatileOrganicCompounds, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.CarbonMonoxide)
+ .Map(x => x.Ozone, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ // --------------- Mappings with messaging types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.CarbonDioxide)
+ .Map(x => x.VolatileOrganicCompounds, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.CarbonMonoxide)
+ .Map(x => x.Ozone, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.CarbonDioxide)
+ .Map(x => x.VolatileOrganicCompounds, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.CarbonMonoxide)
+ .Map(x => x.Ozone, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ // --------------- Mappings with dtos types ---------------
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.CarbonDioxide)
+ .Map(x => x.VolatileOrganicCompounds, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.CarbonMonoxide)
+ .Map(x => x.Ozone, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(x => x.ID, y => y.ID)
+ .Map(x => x.DeviceNumber, y => y.DeviceNumber)
+ .Map(x => x.MeasurementCaptureDate, y => y.MeasurementCaptureDate)
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.CarbonDioxide, y => y.CarbonDioxide)
+ .Map(x => x.VolatileOrganicCompounds, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2v5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.CarbonMonoxide, y => y.CarbonMonoxide)
+ .Map(x => x.Ozone, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.Oxygen, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+ }
+}
diff --git a/Services/Measurements/Measurements.Application/Measurements/CreateMeasurement/CreateMeasurementHandler.cs b/Services/Measurements/Measurements.Application/Measurements/CreateMeasurement/CreateMeasurementHandler.cs
index 0b90b91..b44a7f9 100644
--- a/Services/Measurements/Measurements.Application/Measurements/CreateMeasurement/CreateMeasurementHandler.cs
+++ b/Services/Measurements/Measurements.Application/Measurements/CreateMeasurement/CreateMeasurementHandler.cs
@@ -1,13 +1,11 @@
-using CommonServiceLibrary.Messaging.TopicMessages.Measurements;
-
-namespace Measurements.Application.Measurements.CreateMeasurement;
+namespace Measurements.Application.Measurements.CreateMeasurement;
public class CreateMeasurementHandler(Container cosmosContainer, IPublishEndpoint massTransitPublisher, IHubContext signalRHub) : IRequestHandler
{
public async Task Handle(CreateMeasurementCommand request, CancellationToken cancellationToken)
{
var measurement = request.CreateMeasurement.Adapt();
- measurement.RecordedAt = DateTime.UtcNow;
+ measurement.MeasurementCaptureDate = DateTime.UtcNow;
measurement.ID = Guid.NewGuid();
var response = await cosmosContainer.CreateItemAsync(measurement);
@@ -17,10 +15,11 @@ public async Task Handle(CreateMeasurementCommand reques
}
var measurementDto = response.Resource.Adapt();
+ var messageMeasurement = response.Resource.Adapt();
var message = new MeasurementCreated()
{
- Measurement = measurementDto,
+ Measurement = messageMeasurement,
};
await massTransitPublisher.Publish(message, cancellationToken);
diff --git a/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementCommands.cs b/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementCommands.cs
index a67358e..a4a4369 100644
--- a/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementCommands.cs
+++ b/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementCommands.cs
@@ -24,7 +24,7 @@ public GetMeasurementCommandValidator()
///
///
///
-public record GetAllMeasurementCommand(int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, int? LocationID) : IRequest;
+public record GetAllMeasurementCommand(int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, Guid? LocationHash) : IRequest;
public record GetAllMeasurementResponse(PaginatedList PaginatedMeasurements);
public class GetAllMeasurementCommandValidator : AbstractValidator
{
@@ -33,7 +33,7 @@ public GetAllMeasurementCommandValidator()
}
}
-public record GetAllCombinedMeasurementCommand(int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, int? LocationID) : IRequest;
+public record GetAllCombinedMeasurementCommand(int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, Guid? LocationHash) : IRequest;
public record GetAllCombinedMeasurementResponse(PaginatedList PaginatedMeasurements);
public class GetAllCombinedMeasurementCommandValidator : AbstractValidator
{
diff --git a/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementHandler.cs b/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementHandler.cs
index 9542d29..e5e490b 100644
--- a/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementHandler.cs
+++ b/Services/Measurements/Measurements.Application/Measurements/GetMeasurement/GetMeasurementHandler.cs
@@ -1,4 +1,4 @@
-using Devices.Domain.Models;
+using CommonServiceLibrary.GRPC.Types.Devices;
namespace Measurements.Application.Measurements.GetMeasurement;
@@ -42,17 +42,17 @@ public async Task Handle(GetAllMeasurementCommand req
if (request.DateStart is not null)
{
- query = query.Where(x => x.RecordedAt >= request.DateStart);
+ query = query.Where(x => x.MeasurementCaptureDate >= request.DateStart);
}
if (request.DateEnd is not null)
{
- query = query.Where(x => x.RecordedAt <= request.DateEnd);
+ query = query.Where(x => x.MeasurementCaptureDate <= request.DateEnd);
}
- if (request.LocationID is not null)
+ if (request.LocationHash is not null)
{
- query = query.Where(x => x.LocationID == request.LocationID);
+ query = query.Where(x => x.LocationHash == request.LocationHash);
}
// Order
@@ -60,11 +60,11 @@ public async Task Handle(GetAllMeasurementCommand req
{
if (request.SortOrder == "asc")
{
- query = query.OrderBy(x => x.RecordedAt);
+ query = query.OrderBy(x => x.MeasurementCaptureDate);
}
else
{
- query = query.OrderByDescending(x => x.RecordedAt);
+ query = query.OrderByDescending(x => x.MeasurementCaptureDate);
}
}
@@ -86,24 +86,24 @@ public async Task Handle(GetAllCombinedMeasur
var orederedQuery = cosmosContainer.GetItemLinqQueryable(allowSynchronousQueryExecution: true);
var query = orederedQuery.AsQueryable();
- var devices = new List();
+ var devices = new List();
using (var call = devicesGrpc.GetAllDevices(new DeviceAllRequest()))
{
while (await call.ResponseStream.MoveNext(cancellationToken))
{
var current = call.ResponseStream.Current;
- var dev = current.Adapt();
+ var dev = current.Adapt();
devices.Add(dev);
}
}
- var locations = new List();
+ var locations = new List();
using (var call = devicesGrpc.GetAllLocations(new LocationAllRequest()))
{
while (await call.ResponseStream.MoveNext(cancellationToken))
{
var current = call.ResponseStream.Current;
- var loc = current.Adapt();
+ var loc = current.Adapt();
locations.Add(loc);
}
}
@@ -116,17 +116,17 @@ public async Task Handle(GetAllCombinedMeasur
if (request.DateStart is not null)
{
- query = query.Where(x => x.RecordedAt >= request.DateStart);
+ query = query.Where(x => x.MeasurementCaptureDate >= request.DateStart);
}
if (request.DateEnd is not null)
{
- query = query.Where(x => x.RecordedAt <= request.DateEnd);
+ query = query.Where(x => x.MeasurementCaptureDate <= request.DateEnd);
}
- if (request.LocationID is not null)
+ if (request.LocationHash is not null)
{
- query = query.Where(x => x.LocationID == request.LocationID);
+ query = query.Where(x => x.LocationHash == request.LocationHash);
}
// Order
@@ -134,11 +134,11 @@ public async Task Handle(GetAllCombinedMeasur
{
if (request.SortOrder == "asc")
{
- query = query.OrderBy(x => x.RecordedAt);
+ query = query.OrderBy(x => x.MeasurementCaptureDate);
}
else
{
- query = query.OrderByDescending(x => x.RecordedAt);
+ query = query.OrderByDescending(x => x.MeasurementCaptureDate);
}
}
@@ -149,15 +149,15 @@ public async Task Handle(GetAllCombinedMeasur
var measurementCombinedDtos = query.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize).ToList().Select(x =>
{
var device = devices.FirstOrDefault(y => y.DeviceNumber == x.DeviceNumber);
- var location = locations.FirstOrDefault(y => y.ID == x.LocationID);
+ var location = locations.FirstOrDefault(y => y.Hash == x.LocationHash);
return new CombinedMeasurementDTO(
x.ID,
- x.RecordedAt,
+ x.MeasurementCaptureDate,
(device != null) ? device.ID : -1,
(device != null) ? device.Name : "",
x.DeviceNumber,
- x.LocationID,
+ x.LocationHash,
(location != null) ? location.Name : "",
x.Temperature,
x.Humidity,
diff --git a/Services/Measurements/Measurements.Application/Measurements/MeasurementEndpoints.cs b/Services/Measurements/Measurements.Application/Measurements/MeasurementEndpoints.cs
index d635bcd..a19e41e 100644
--- a/Services/Measurements/Measurements.Application/Measurements/MeasurementEndpoints.cs
+++ b/Services/Measurements/Measurements.Application/Measurements/MeasurementEndpoints.cs
@@ -22,18 +22,18 @@ public void AddRoutes(IEndpointRouteBuilder app)
return Results.Ok(dto);
});
- app.MapGet("/measurements/all/", async (ISender sender, int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, int? LocationID) =>
+ app.MapGet("/measurements/all/", async (ISender sender, int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, Guid? LocationHash) =>
{
- var response = await sender.Send(new GetAllMeasurementCommand(Page, PageSize, SortOrder, DeviceNumber, DateStart, DateEnd, LocationID));
+ var response = await sender.Send(new GetAllMeasurementCommand(Page, PageSize, SortOrder, DeviceNumber, DateStart, DateEnd, LocationHash));
var paginatedDtos = response.PaginatedMeasurements;
return Results.Ok(paginatedDtos);
});
- app.MapGet("/measurements/combined/all/", async (ISender sender, int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, int? LocationID) =>
+ app.MapGet("/measurements/combined/all/", async (ISender sender, int Page, int PageSize, string? SortOrder, Guid? DeviceNumber, DateTime? DateStart, DateTime? DateEnd, Guid? LocationHash) =>
{
- var response = await sender.Send(new GetAllCombinedMeasurementCommand(Page, PageSize, SortOrder, DeviceNumber, DateStart, DateEnd, LocationID));
+ var response = await sender.Send(new GetAllCombinedMeasurementCommand(Page, PageSize, SortOrder, DeviceNumber, DateStart, DateEnd, LocationHash));
var paginatedDtos = response.PaginatedMeasurements;
diff --git a/Services/Measurements/Measurements.DataTransferObjects/MeasurementDTO.cs b/Services/Measurements/Measurements.DataTransferObjects/MeasurementDTO.cs
index f96b393..5a2ad75 100644
--- a/Services/Measurements/Measurements.DataTransferObjects/MeasurementDTO.cs
+++ b/Services/Measurements/Measurements.DataTransferObjects/MeasurementDTO.cs
@@ -4,10 +4,10 @@
/// Represents a request to create a new environmental measurement record.
///
public record CreateMeasurementDTO(
+ Guid ID,
Guid DeviceNumber,
- int LocationID,
-
- DateTime Date,
+ DateTime MeasurementCaptureDate,
+ Guid LocationHash,
/// Temperature measured in degrees Celsius (°C).
double? Temperature,
@@ -67,8 +67,8 @@ public record CreateMeasurementDTO(
public record DefaultMeasurementDTO(
Guid ID,
Guid DeviceNumber,
- int LocationID,
- DateTime Date,
+ Guid LocationHash,
+ DateTime MeasurementCaptureDate,
/// Temperature measured in degrees Celsius (°C).
double? Temperature,
@@ -154,7 +154,7 @@ public record CombinedMeasurementDTO(
Guid ID,
/// Date of measurement registration.
- DateTime Date,
+ DateTime MeasurementCaptureDate,
/// ID of device that captured data.
int DeviceID,
@@ -166,7 +166,7 @@ public record CombinedMeasurementDTO(
Guid DeviceNumber,
/// ID of location where the data was captured.
- int LocationID,
+ Guid LocationHash,
/// Name of location where the data was captured.
string LocationName,
diff --git a/Services/Measurements/Measurements.Domain/Models/Measurement.cs b/Services/Measurements/Measurements.Domain/Models/Measurement.cs
index f1506dc..005ebbf 100644
--- a/Services/Measurements/Measurements.Domain/Models/Measurement.cs
+++ b/Services/Measurements/Measurements.Domain/Models/Measurement.cs
@@ -1,95 +1,94 @@
-namespace Measurements.Domain.Models
+namespace Measurements.Domain.Models;
+
+public class Measurement
{
- public class Measurement
- {
- public Guid ID { get; set; }
- public Guid DeviceNumber { get; set; }
- public DateTime RecordedAt { get; set; }
- public int LocationID { get; set; }
-
- ///
- /// Temperature measured in degrees Celsius (°C).
- ///
- public double? Temperature { get; set; }
-
- ///
- /// Relative humidity measured as a percentage (% RH).
- ///
- public double? Humidity { get; set; }
-
- ///
- /// Carbon dioxide concentration measured in parts per million (ppm).
- ///
- public double? CarbonDioxide { get; set; }
-
- ///
- /// Volatile organic compounds (VOCs) concentration measured in micrograms per cubic meter (µg/m³).
- ///
- public double? VolatileOrganicCompounds { get; set; }
-
- ///
- /// Particulate matter (PM1) concentration measured in micrograms per cubic meter (µg/m³).
- ///
- public double? ParticulateMatter1 { get; set; }
-
- ///
- /// Particulate matter (PM2.5) concentration measured in micrograms per cubic meter (µg/m³).
- ///
- public double? ParticulateMatter2v5 { get; set; }
-
- ///
- /// Particulate matter (PM10) concentration measured in micrograms per cubic meter (µg/m³).
- ///
- public double? ParticulateMatter10 { get; set; }
-
- ///
- /// Formaldehyde concentration measured in micrograms per cubic meter (µg/m³).
- ///
- public double? Formaldehyde { get; set; }
-
- ///
- /// Carbon monoxide concentration measured in parts per million (ppm).
- ///
- public double? CarbonMonoxide { get; set; }
-
- ///
- /// Ozone concentration measured in parts per billion (ppb).
- ///
- public double? Ozone { get; set; }
-
- ///
- /// Ammonia concentration measured in milligrams per cubic meter (mg/m³).
- ///
- public double? Ammonia { get; set; }
-
- ///
- /// Airflow rate measured in cubic feet per minute (CFM).
- ///
- public double? Airflow { get; set; }
-
- ///
- /// Air ionization level measured in ions per cubic centimeter (ions/cm³).
- ///
- public double? AirIonizationLevel { get; set; }
-
- ///
- /// Oxygen concentration measured as a percentage (%).
- ///
- public double? Oxygen { get; set; }
-
- ///
- /// Radon concentration measured in becquerels per cubic meter (Bq/m³).
- ///
- public double? Radon { get; set; }
-
- ///
- /// Illuminance measured in lux (lx).
- ///
- public double? Illuminance { get; set; }
-
- ///
- /// Sound level measured in decibels (dB).
- ///
- public double? SoundLevel { get; set; }
- }
+ public Guid ID { get; set; }
+ public Guid DeviceNumber { get; set; }
+ public DateTime MeasurementCaptureDate { get; set; }
+ public Guid LocationHash { get; set; }
+
+ ///
+ /// Temperature measured in degrees Celsius (°C).
+ ///
+ public double? Temperature { get; set; }
+
+ ///
+ /// Relative humidity measured as a percentage (% RH).
+ ///
+ public double? Humidity { get; set; }
+
+ ///
+ /// Carbon dioxide concentration measured in parts per million (ppm).
+ ///
+ public double? CarbonDioxide { get; set; }
+
+ ///
+ /// Volatile organic compounds (VOCs) concentration measured in micrograms per cubic meter (µg/m³).
+ ///
+ public double? VolatileOrganicCompounds { get; set; }
+
+ ///
+ /// Particulate matter (PM1) concentration measured in micrograms per cubic meter (µg/m³).
+ ///
+ public double? ParticulateMatter1 { get; set; }
+
+ ///
+ /// Particulate matter (PM2.5) concentration measured in micrograms per cubic meter (µg/m³).
+ ///
+ public double? ParticulateMatter2v5 { get; set; }
+
+ ///
+ /// Particulate matter (PM10) concentration measured in micrograms per cubic meter (µg/m³).
+ ///
+ public double? ParticulateMatter10 { get; set; }
+
+ ///
+ /// Formaldehyde concentration measured in micrograms per cubic meter (µg/m³).
+ ///
+ public double? Formaldehyde { get; set; }
+
+ ///
+ /// Carbon monoxide concentration measured in parts per million (ppm).
+ ///
+ public double? CarbonMonoxide { get; set; }
+
+ ///
+ /// Ozone concentration measured in parts per billion (ppb).
+ ///
+ public double? Ozone { get; set; }
+
+ ///
+ /// Ammonia concentration measured in milligrams per cubic meter (mg/m³).
+ ///
+ public double? Ammonia { get; set; }
+
+ ///
+ /// Airflow rate measured in cubic feet per minute (CFM).
+ ///
+ public double? Airflow { get; set; }
+
+ ///
+ /// Air ionization level measured in ions per cubic centimeter (ions/cm³).
+ ///
+ public double? AirIonizationLevel { get; set; }
+
+ ///
+ /// Oxygen concentration measured as a percentage (%).
+ ///
+ public double? Oxygen { get; set; }
+
+ ///
+ /// Radon concentration measured in becquerels per cubic meter (Bq/m³).
+ ///
+ public double? Radon { get; set; }
+
+ ///
+ /// Illuminance measured in lux (lx).
+ ///
+ public double? Illuminance { get; set; }
+
+ ///
+ /// Sound level measured in decibels (dB).
+ ///
+ public double? SoundLevel { get; set; }
}
diff --git a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/DependencyInjection.cs b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/DependencyInjection.cs
index 3581894..4223b92 100644
--- a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/DependencyInjection.cs
+++ b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/DependencyInjection.cs
@@ -6,6 +6,12 @@ public static IServiceCollection AddGRPCServerServices(this IServiceCollection s
{
services.AddGrpc();
+ var mappingConfiguration = new TypeAdapterConfig();
+
+ mappingConfiguration.Apply(new MeasurementMapper());
+
+ services.AddSingleton(mappingConfiguration);
+
return services;
}
diff --git a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/GlobalUsings.cs b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/GlobalUsings.cs
index 2837345..dc7a5a3 100644
--- a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/GlobalUsings.cs
+++ b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/GlobalUsings.cs
@@ -1,6 +1,7 @@
global using Grpc.Core;
global using Mapster;
global using Measurements.Domain.Models;
+global using Measurements.GRPCServer.Mappings;
global using Measurements.GRPCServer.Services;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.Extensions.Configuration;
diff --git a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Mappings/MeasurementMapper.cs b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Mappings/MeasurementMapper.cs
new file mode 100644
index 0000000..5f6a2ff
--- /dev/null
+++ b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Mappings/MeasurementMapper.cs
@@ -0,0 +1,59 @@
+namespace Measurements.GRPCServer.Mappings;
+
+internal class MeasurementMapper : IRegister
+{
+ public void Register(TypeAdapterConfig config)
+ {
+ // gRPC server mapping for Measurement
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(dest => dest.Id, src => src.ID)
+ .Map(dest => dest.DeviceNumber, src => src.DeviceNumber)
+ .Map(dest => dest.MeasurementCaptureDate, src => src.MeasurementCaptureDate.ToString("o"))
+ .Map(x => x.LocationHash, y => y.LocationHash)
+ .Map(x => x.Temperature, y => y.Temperature)
+ .Map(x => x.Humidity, y => y.Humidity)
+ .Map(x => x.Co2, y => y.CarbonDioxide)
+ .Map(x => x.Voc, y => y.VolatileOrganicCompounds)
+ .Map(x => x.ParticulateMatter1, y => y.ParticulateMatter1)
+ .Map(x => x.ParticulateMatter2V5, y => y.ParticulateMatter2v5)
+ .Map(x => x.ParticulateMatter10, y => y.ParticulateMatter10)
+ .Map(x => x.Formaldehyde, y => y.Formaldehyde)
+ .Map(x => x.Co, y => y.CarbonMonoxide)
+ .Map(x => x.O3, y => y.Ozone)
+ .Map(x => x.Ammonia, y => y.Ammonia)
+ .Map(x => x.Airflow, y => y.Airflow)
+ .Map(x => x.AirIonizationLevel, y => y.AirIonizationLevel)
+ .Map(x => x.O2, y => y.Oxygen)
+ .Map(x => x.Radon, y => y.Radon)
+ .Map(x => x.Illuminance, y => y.Illuminance)
+ .Map(x => x.SoundLevel, y => y.SoundLevel);
+
+ TypeAdapterConfig
+ .NewConfig()
+ .Map(dest => dest.ID, src => src.Id)
+ .Map(dest => dest.DeviceNumber, src => src.DeviceNumber)
+ .Map(dest => dest.MeasurementCaptureDate, src => src.MeasurementCaptureDate)
+ .Map(dest => dest.LocationHash, src => src.LocationHash)
+
+ .Map(dest => dest.Temperature, src => src.Temperature)
+ .Map(dest => dest.Humidity, src => src.Humidity)
+ .Map(dest => dest.CarbonDioxide, src => src.Co2)
+ .Map(dest => dest.VolatileOrganicCompounds, src => src.Voc)
+ .Map(dest => dest.ParticulateMatter1, src => src.ParticulateMatter1)
+ .Map(dest => dest.ParticulateMatter2v5, src => src.ParticulateMatter2V5)
+ .Map(dest => dest.ParticulateMatter10, src => src.ParticulateMatter10)
+ .Map(dest => dest.Formaldehyde, src => src.Formaldehyde)
+ .Map(dest => dest.CarbonMonoxide, src => src.Co)
+ .Map(dest => dest.Ozone, src => src.O3)
+ .Map(dest => dest.Ammonia, src => src.Ammonia)
+ .Map(dest => dest.Airflow, src => src.Airflow)
+ .Map(dest => dest.AirIonizationLevel, src => src.AirIonizationLevel)
+ .Map(dest => dest.Oxygen, src => src.O2)
+ .Map(dest => dest.Radon, src => src.Radon)
+ .Map(dest => dest.Illuminance, src => src.Illuminance)
+ .Map(dest => dest.SoundLevel, src => src.SoundLevel);
+
+ }
+}
diff --git a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Measurements.GRPCServer.csproj b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Measurements.GRPCServer.csproj
index 788f3de..947ad94 100644
--- a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Measurements.GRPCServer.csproj
+++ b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Measurements.GRPCServer.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Protos/measurements.proto b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Protos/measurements.proto
index 89ca002..25e06d5 100644
--- a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Protos/measurements.proto
+++ b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Protos/measurements.proto
@@ -5,48 +5,39 @@ option csharp_namespace = "Measurements.GRPCServer";
package measurements;
service MeasurementService {
- rpc MeasurementByID (MeasurementRequest) returns (MeasurementGrpcModel);
- rpc MeasurementsAllByDay (MeasurementByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsAllByWeek (MeasurementByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsAllByMonth (MeasurementByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsByDay (MeasurementFromDeviceByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsByWeek (MeasurementFromDeviceByDateRequest) returns (stream MeasurementGrpcModel);
- rpc MeasurementsByMonth (MeasurementFromDeviceByDateRequest) returns (stream MeasurementGrpcModel);
+ rpc MeasurementsQuery (MeasurementsQueryRequest) returns (stream MeasurementGrpcModel);
}
-message MeasurementByDateRequest {
- string date = 1;
-}
+message MeasurementsQueryRequest {
+ string sort_order = 1;
+ optional string start_date = 2;
+ optional string end_date = 3;
-message MeasurementFromDeviceByDateRequest {
- string device_number = 1;
- string date = 2;
-}
-
-message MeasurementRequest {
- string id = 1;
+ repeated string device_numbers = 4;
+ repeated string location_hashes = 5;
}
message MeasurementGrpcModel {
string id = 1;
string device_number = 2;
- string register_date = 3;
-
- optional double temperature = 4;
- optional double humidity = 5;
- optional double co2 = 6;
- optional double voc = 7;
- optional double particulate_matter1 = 8;
- optional double particulate_matter2v5 = 9;
- optional double particulate_matter10 = 10;
- optional double formaldehyde = 11;
- optional double co = 12;
- optional double o3 = 13;
- optional double ammonia = 14;
- optional double airflow = 15;
- optional double air_ionization_level = 16;
- optional double o2 = 17;
- optional double radon = 18;
- optional double illuminance = 19;
- optional double sound_level = 20;
+ string measurement_capture_date = 3;
+ string location_hash = 4;
+
+ optional double temperature = 5;
+ optional double humidity = 6;
+ optional double co2 = 7;
+ optional double voc = 8;
+ optional double particulate_matter1 = 9;
+ optional double particulate_matter2v5 = 10;
+ optional double particulate_matter10 = 11;
+ optional double formaldehyde = 12;
+ optional double co = 13;
+ optional double o3 = 14;
+ optional double ammonia = 15;
+ optional double airflow = 16;
+ optional double air_ionization_level = 17;
+ optional double o2 = 18;
+ optional double radon = 19;
+ optional double illuminance = 20;
+ optional double sound_level = 21;
}
\ No newline at end of file
diff --git a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Services/MeasurementsServerService.cs b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Services/MeasurementsServerService.cs
index b24bd4d..ee0b5c2 100644
--- a/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Services/MeasurementsServerService.cs
+++ b/Services/Measurements/Measurements.GRPCServer/Measurements.GRPCServer/Services/MeasurementsServerService.cs
@@ -1,91 +1,85 @@
-namespace Measurements.GRPCServer.Services;
+using Microsoft.Azure.Cosmos;
-public class MeasurementsServerService(ILogger logger, object dbcontext) : MeasurementService.MeasurementServiceBase
-{
- public override async Task MeasurementByID(MeasurementRequest request, ServerCallContext context)
- {
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- Guid id = Guid.Parse(request.Id);
- //var dbModel = await dbcontext.GetMeasurement(id);
-
- //var grpcModel = dbModel.Adapt();
-
- return null;
- }
-
- public override async Task MeasurementsAllByDay(MeasurementByDateRequest request, IServerStreamWriter responseStream, ServerCallContext context)
- {
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- DateTime day = ParseDateTime(request.Date);
-
- //var dbMeasurements = await dbcontext.GetAllMeasurementsFromDay(day);
-
- //var grpsMeasurements = dbMeasurements.Adapt>();
-
- // await SendData(grpsMeasurements, responseStream, context);
- }
-
- public override async Task MeasurementsAllByWeek(MeasurementByDateRequest request, IServerStreamWriter responseStream, ServerCallContext context)
- {
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- DateTime day = ParseDateTime(request.Date);
-
- //var dbMeasurements = await dbcontext.GetAllMeasurementsFromWeek(day);
-
- //var grpsMeasurements = dbMeasurements.Adapt>();
-
- //await SendData(grpsMeasurements, responseStream, context);
- }
-
- public override async Task MeasurementsAllByMonth(MeasurementByDateRequest request, IServerStreamWriter responseStream, ServerCallContext context)
- {
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- DateTime day = ParseDateTime(request.Date);
-
- //var dbMeasurements = await dbcontext.GetAllMeasurementsFromMonth(day);
-
- //var grpsMeasurements = dbMeasurements.Adapt>();
+namespace Measurements.GRPCServer.Services;
- //await SendData(grpsMeasurements, responseStream, context);
- }
-
- public override async Task MeasurementsByDay(MeasurementFromDeviceByDateRequest request, IServerStreamWriter responseStream, ServerCallContext context)
+public class MeasurementsServerService(ILogger logger, Container database) : MeasurementService.MeasurementServiceBase
+{
+ public override async Task MeasurementsQuery(MeasurementsQueryRequest request, IServerStreamWriter responseStream, ServerCallContext context)
{
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- DateTime day = ParseDateTime(request.Date);
- Guid deviceNumber = ParseIdentifier(request.DeviceNumber);
-
- //var dbMeasurements = await dbcontext.GetMeasurementsFromDay(deviceNumber, day);
-
- //var grpsMeasurements = dbMeasurements.Adapt>();
+ var orederedQuery = database.GetItemLinqQueryable(allowSynchronousQueryExecution: true);
+ var query = orederedQuery.AsQueryable();
- //await SendData(grpsMeasurements, responseStream, context);
- }
+ // Device numbers
+ if (request.DeviceNumbers is not null && request.DeviceNumbers.Count != 0)
+ {
+ var deviceNumbers = request.DeviceNumbers.Select(x => Guid.Parse(x)).ToList();
- public override async Task MeasurementsByWeek(MeasurementFromDeviceByDateRequest request, IServerStreamWriter responseStream, ServerCallContext context)
- {
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- DateTime day = ParseDateTime(request.Date);
- Guid deviceNumber = ParseIdentifier(request.DeviceNumber);
+ query = query.Where(x => deviceNumbers.Contains(x.DeviceNumber));
+ }
- //var dbMeasurements = await dbcontext.GetMeasurementsFromWeek(deviceNumber, day);
+ // Locations
+ if (request.LocationHashes is not null && request.LocationHashes.Count != 0)
+ {
+ var locationHashes = request.LocationHashes.Select(x => Guid.Parse(x)).ToList();
- //var grpsMeasurements = dbMeasurements.Adapt>();
+ query = query.Where(x => locationHashes.Contains(x.LocationHash));
+ }
- //await SendData(grpsMeasurements, responseStream, context);
- }
+ if (request.HasStartDate && request.StartDate is not null && request.StartDate != string.Empty)
+ {
+ var startDate = DateTime.Parse(request.StartDate);
+ query = query.Where(x => x.MeasurementCaptureDate >= startDate);
+ }
- public override async Task MeasurementsByMonth(MeasurementFromDeviceByDateRequest request, IServerStreamWriter responseStream, ServerCallContext context)
- {
- throw new NotImplementedException("TODO: We have changed measurement data definition so we have to rewrite that!");
- DateTime day = ParseDateTime(request.Date);
- Guid deviceNumber = ParseIdentifier(request.DeviceNumber);
+ if (request.HasEndDate && request.EndDate is not null && request.EndDate != string.Empty)
+ {
+ var endDate = DateTime.Parse(request.EndDate).AddTicks(1);
+ query = query.Where(x => x.MeasurementCaptureDate <= endDate);
+ }
- //var dbMeasurements = await dbcontext.GetMeasurementsFromMonth(deviceNumber, day);
+ // Order
+ if (request.SortOrder is not null)
+ {
+ if (request.SortOrder == "asc")
+ {
+ query = query.OrderBy(x => x.MeasurementCaptureDate);
+ }
+ else
+ {
+ query = query.OrderByDescending(x => x.MeasurementCaptureDate);
+ }
+ }
- //var grpsMeasurements = dbMeasurements.Adapt>();
+ var queryResult = query.ToList();
- //await SendData(grpsMeasurements, responseStream, context);
+ var dtoResponse = queryResult.Adapt>();
+ var alternativeResp = queryResult.Select(x => new MeasurementGrpcModel()
+ {
+ Id = x.ID.ToString(),
+ DeviceNumber = x.DeviceNumber.ToString(),
+ MeasurementCaptureDate = x.MeasurementCaptureDate.ToString("o"),
+ LocationHash = x.LocationHash.ToString(),
+
+ Temperature = x.Temperature is not null ? (double)x.Temperature : double.MinValue,
+ Humidity = x.Humidity is not null ? (double)x.Humidity : double.MinValue,
+ Co2 = x.CarbonDioxide is not null ? (double)x.CarbonDioxide : double.MinValue,
+ Voc = x.VolatileOrganicCompounds is not null ? (double)x.VolatileOrganicCompounds : double.MinValue,
+ ParticulateMatter1 = x.ParticulateMatter1 is not null ? (double)x.ParticulateMatter1 : double.MinValue,
+ ParticulateMatter2V5 = x.ParticulateMatter2v5 is not null ? (double)x.ParticulateMatter2v5 : double.MinValue,
+ ParticulateMatter10 = x.ParticulateMatter10 is not null ? (double)x.ParticulateMatter10 : double.MinValue,
+ Formaldehyde = x.Formaldehyde is not null ? (double)x.Formaldehyde : double.MinValue,
+ Co = x.CarbonMonoxide is not null ? (double)x.CarbonMonoxide : double.MinValue,
+ O3 = x.Ozone is not null ? (double)x.Ozone : double.MinValue,
+ Ammonia = x.Ammonia is not null ? (double)x.Ammonia : double.MinValue,
+ Airflow = x.Airflow is not null ? (double)x.Airflow : double.MinValue,
+ AirIonizationLevel = x.AirIonizationLevel is not null ? (double)x.AirIonizationLevel : double.MinValue,
+ O2 = x.Oxygen is not null ? (double)x.Oxygen : double.MinValue,
+ Radon = x.Radon is not null ? (double)x.Radon : double.MinValue,
+ Illuminance = x.Illuminance is not null ? (double)x.Illuminance : double.MinValue,
+ SoundLevel = x.SoundLevel is not null ? (double)x.SoundLevel : double.MinValue,
+ });
+
+ await SendData(alternativeResp, responseStream, context);
}
private async Task SendData(IEnumerable Data, IServerStreamWriter responseStream, ServerCallContext context)
diff --git a/Services/Measurements/Measurements.Infrastructure/DependencyInjection.cs b/Services/Measurements/Measurements.Infrastructure/DependencyInjection.cs
index ad8e320..6fbb20e 100644
--- a/Services/Measurements/Measurements.Infrastructure/DependencyInjection.cs
+++ b/Services/Measurements/Measurements.Infrastructure/DependencyInjection.cs
@@ -17,6 +17,7 @@ public static IServiceCollection AddInfrastructureServices(this IServiceCollecti
ConnectionMode = environment.IsProduction() ? ConnectionMode.Direct : ConnectionMode.Gateway,
SerializerOptions = new CosmosSerializationOptions
{
+ IgnoreNullValues = true,
PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
}
});
diff --git a/Services/Raports/Raports.API/Dockerfile b/Services/Raports/Raports.API/Dockerfile
index 092e0aa..a5cd4c0 100644
--- a/Services/Raports/Raports.API/Dockerfile
+++ b/Services/Raports/Raports.API/Dockerfile
@@ -11,7 +11,6 @@ COPY ["Services/Raports/Raports.API/Raports.API.csproj", "Services/Raports/Rapor
COPY ["Services/Raports/Raports.Application/Raports.Application.csproj", "Services/Raports/Raports.Application/"]
COPY ["Common/CommonServiceLibrary.Messaging/CommonServiceLibrary.Messaging.csproj", "Common/CommonServiceLibrary.Messaging/"]
COPY ["Services/Raports/Raports.Infrastructure/Raports.Infrastructure.csproj", "Services/Raports/Raports.Infrastructure/"]
-COPY ["Common/CommonServiceLibrary.BlobStorage/CommonServiceLibrary.BlobStorage.csproj", "Common/CommonServiceLibrary.BlobStorage/"]
COPY ["Common/CommonServiceLibrary.GRPC/CommonServiceLibrary.GRPC.csproj", "Common/CommonServiceLibrary.GRPC/"]
COPY ["Common/CommonServiceLibrary/CommonServiceLibrary.csproj", "Common/CommonServiceLibrary/"]
COPY ["Services/Raports/Raports.Domain/Raports.Domain.csproj", "Services/Raports/Raports.Domain/"]
@@ -25,6 +24,21 @@ ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Raports.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
+
+USER root
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ fontconfig \
+ fonts-dejavu-core \
+ fonts-liberation \
+ libgdiplus \
+ libfreetype6 \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN fc-cache -fv
+
+USER $APP_UID
+
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Raports.API.dll"]
\ No newline at end of file
diff --git a/Services/Raports/Raports.API/GlobalUsings.cs b/Services/Raports/Raports.API/GlobalUsings.cs
index fd7db61..ed53b5a 100644
--- a/Services/Raports/Raports.API/GlobalUsings.cs
+++ b/Services/Raports/Raports.API/GlobalUsings.cs
@@ -1,6 +1,4 @@
global using HealthChecks.UI.Client;
global using Microsoft.AspNetCore.Diagnostics.HealthChecks;
global using Raports.Application;
-global using Raports.Infrastructure;
-global using Raports.Infrastructure.Extensions;
-
+global using Raports.Infrastructure;
\ No newline at end of file
diff --git a/Services/Raports/Raports.API/Program.cs b/Services/Raports/Raports.API/Program.cs
index 491167a..bd0a4d2 100644
--- a/Services/Raports/Raports.API/Program.cs
+++ b/Services/Raports/Raports.API/Program.cs
@@ -1,7 +1,7 @@
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddInfrastructureServices(builder.Configuration, builder.Environment);
-builder.Services.AddApplicationServices(builder.Configuration);
+builder.Services.AddApplicationServices(builder.Configuration, builder.Environment);
builder.Services.AddHealthChecks();
builder.Services.AddCors(options =>
@@ -20,13 +20,6 @@
var app = builder.Build();
-bool initializeDB = app.Configuration["InitializeDBOnStart"] == "true" ? true : false;
-
-if (app.Environment.IsDevelopment() && initializeDB)
-{
- await app.InitializeDatabaseAsync();
-}
-
app.UseApplicationServices();
app.UseCors();
diff --git a/Services/Raports/Raports.Application/Consumers/AdjustRaportConsumer.cs b/Services/Raports/Raports.Application/Consumers/AdjustRaportConsumer.cs
new file mode 100644
index 0000000..0005358
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/AdjustRaportConsumer.cs
@@ -0,0 +1,181 @@
+namespace Raports.Application.Consumers;
+
+internal class AdjustRaportConsumer(ILogger logger,
+ IPublishEndpoint publish,
+ RaportsDBContext database) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation("AdjustRaportConsumer: adjusting RaportID={RaportId}", context.Message.Raport.ID);
+
+ var ct = context.CancellationToken;
+
+ // helper to publish RaportFailed
+ async Task PublishRaportFailedAsync(DefaultRaportDTO raportDto, int raportId, string description, CancellationToken cancellationToken)
+ {
+ try
+ {
+ logger.LogWarning("AdjustRaportConsumer: publishing RaportFailed for Raport {RaportId}: {Description}", raportId, description);
+ var message = new RaportFailed()
+ {
+ FailedDate = DateTime.UtcNow,
+ Description = description,
+ Raport = raportDto
+ };
+
+ await publish.Publish(message, cancellationToken);
+ logger.LogInformation("AdjustRaportConsumer: RaportFailed published for Raport {RaportId}", raportId);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "AdjustRaportConsumer: error while publishing RaportFailed for Raport {RaportId}", raportId);
+ }
+ }
+
+ try
+ {
+ var dbRaport = await database.Raports
+ .Include(r => r.Period)
+ // include measurement and location on measurement groups
+ .Include(r => r.MeasurementGroups).ThenInclude(mg => mg.Measurement)
+ .Include(r => r.MeasurementGroups).ThenInclude(mg => mg.LocationGroups).ThenInclude(lg => lg.Location)
+ .Include(r => r.MeasurementGroups).ThenInclude(mg => mg.LocationGroups).ThenInclude(lg => lg.SampleGroups)
+ .FirstOrDefaultAsync(r => r.ID == context.Message.Raport.ID, ct);
+
+ if (dbRaport is null)
+ {
+ logger.LogWarning("AdjustRaportConsumer: Raport {RaportId} not found", context.Message.Raport.ID);
+ return;
+ }
+
+ var period = dbRaport.Period ?? await database.Periods.FirstOrDefaultAsync(p => p.ID == dbRaport.PeriodID, ct);
+ var timeframe = period?.TimeFrame ?? TimeSpan.FromHours(1);
+ if (timeframe <= TimeSpan.Zero) timeframe = TimeSpan.FromHours(1);
+
+ var start = dbRaport.StartDate;
+ var end = dbRaport.EndDate;
+
+ // canonical timepoints
+ var canonicalTimepoints = new List();
+ for (var tp = start; tp <= end; tp = tp.Add(timeframe)) canonicalTimepoints.Add(tp);
+ if (canonicalTimepoints.Count == 0 || canonicalTimepoints.Last() < end) canonicalTimepoints.Add(end);
+
+ var toAdd = new List();
+
+ foreach (var measurementGroup in dbRaport.MeasurementGroups)
+ {
+ var measurementName = measurementGroup.Measurement?.Name ?? "";
+
+ foreach (var locationGroup in measurementGroup.LocationGroups)
+ {
+ var locationName = locationGroup.Location?.Name ?? "";
+
+ // existing samples for this location group
+ var existing = locationGroup.SampleGroups
+ .OrderBy(s => s.Date)
+ .ToList();
+
+ // quick lookup of existing dates
+ var existingDates = new HashSet(existing.Select(s => s.Date));
+
+ if (existing.Count == 0)
+ {
+ logger.LogWarning("AdjustRaportConsumer: no samples for Measurement '{Measurement}' at Location '{Location}', skipping interpolation", measurementName, locationName);
+ continue;
+ }
+
+ // for each canonical timepoint, if missing - compute value
+ foreach (var tp in canonicalTimepoints)
+ {
+ if (existingDates.Contains(tp)) continue; // already present
+
+ // find neighbours
+ var prev = existing.LastOrDefault(s => s.Date < tp);
+ var next = existing.FirstOrDefault(s => s.Date > tp);
+
+ double? value = null;
+
+ if (prev is not null && next is not null)
+ {
+ var total = (next.Date - prev.Date).TotalSeconds;
+ if (total <= 0)
+ {
+ value = prev.Value; // degenerate
+ }
+ else
+ {
+ var offset = (tp - prev.Date).TotalSeconds;
+ var frac = offset / total;
+ value = prev.Value + (next.Value - prev.Value) * frac;
+ }
+ }
+ else if (prev is not null)
+ {
+ value = prev.Value; // carry forward
+ }
+ else if (next is not null)
+ {
+ value = next.Value; // carry backward
+ }
+
+ if (value.HasValue)
+ {
+ var sample = new SampleGroup
+ {
+ Date = tp,
+ Value = value.Value,
+ LocationGroupID = locationGroup.ID
+ };
+
+ toAdd.Add(sample);
+
+ // Also add to in-memory lists so subsequent points can use them
+ existing.Add(new SampleGroup { Date = tp, Value = value.Value, LocationGroupID = locationGroup.ID });
+ existing = existing.OrderBy(s => s.Date).ToList();
+ existingDates.Add(tp);
+ }
+ else
+ {
+ logger.LogWarning("AdjustRaportConsumer: unable to compute value for Measurement '{Measurement}' at Location '{Location}' for time {Time}", measurementName, locationName, tp);
+ }
+ }
+ }
+ }
+
+ if (toAdd.Count > 0)
+ {
+ try
+ {
+ await database.SampleGroups.AddRangeAsync(toAdd, ct);
+ await database.SaveChangesAsync(ct);
+
+ logger.LogInformation("AdjustRaportConsumer: added {Count} interpolated samples for Raport {RaportId}", toAdd.Count, dbRaport.ID);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "AdjustRaportConsumer: error while saving interpolated samples for Raport {RaportId}", dbRaport.ID);
+ // publish failure
+ await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, "Error while saving interpolated samples: " + ex.Message, ct);
+ return;
+ }
+ }
+ else
+ {
+ logger.LogInformation("AdjustRaportConsumer: no interpolated samples needed for Raport {RaportId}", dbRaport.ID);
+ }
+
+ var message = new GenerateSummary()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, ct);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "AdjustRaportConsumer: unexpected error while adjusting Raport {RaportId}", context.Message.Raport.ID);
+ await PublishRaportFailedAsync(context.Message.Raport, context.Message.Raport.ID, "Unexpected error: " + ex.Message, ct);
+ return;
+ }
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/Document/ProcessDailyDocumentConsumer.cs b/Services/Raports/Raports.Application/Consumers/Document/ProcessDailyDocumentConsumer.cs
new file mode 100644
index 0000000..8a58d56
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/Document/ProcessDailyDocumentConsumer.cs
@@ -0,0 +1,19 @@
+namespace Raports.Application.Consumers.Document;
+
+internal class ProcessDailyDocumentConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation($"Creating document for Daily raport");
+
+ var message = new RaportReady()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, context =>
+ {
+ context.Headers.Set("PeriodName", message.Raport.Period.Name);
+ });
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/Document/ProcessHourlyDocumentConsumer.cs b/Services/Raports/Raports.Application/Consumers/Document/ProcessHourlyDocumentConsumer.cs
new file mode 100644
index 0000000..a18a77c
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/Document/ProcessHourlyDocumentConsumer.cs
@@ -0,0 +1,19 @@
+namespace Raports.Application.Consumers.Document;
+
+internal class ProcessHourlyDocumentConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation($"Creating document for Hourly raport");
+
+ var message = new RaportReady()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, context =>
+ {
+ context.Headers.Set("PeriodName", message.Raport.Period.Name);
+ });
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/Document/ProcessMonthlyDocumentConsumer.cs b/Services/Raports/Raports.Application/Consumers/Document/ProcessMonthlyDocumentConsumer.cs
new file mode 100644
index 0000000..560470e
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/Document/ProcessMonthlyDocumentConsumer.cs
@@ -0,0 +1,19 @@
+namespace Raports.Application.Consumers.Document;
+
+internal class ProcessMonthlyDocumentConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation($"Creating document for Monthly raport");
+
+ var message = new RaportReady()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, context =>
+ {
+ context.Headers.Set("PeriodName", message.Raport.Period.Name);
+ });
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/Document/ProcessWeeklyDocumentConsumer.cs b/Services/Raports/Raports.Application/Consumers/Document/ProcessWeeklyDocumentConsumer.cs
new file mode 100644
index 0000000..bb73006
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/Document/ProcessWeeklyDocumentConsumer.cs
@@ -0,0 +1,19 @@
+namespace Raports.Application.Consumers.Document;
+
+internal class ProcessWeeklyDocumentConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation($"Creating document for Weekly raport");
+
+ var message = new RaportReady()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, context =>
+ {
+ context.Headers.Set("PeriodName", message.Raport.Period.Name);
+ });
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/GenerateDocumentConsumer.cs b/Services/Raports/Raports.Application/Consumers/GenerateDocumentConsumer.cs
new file mode 100644
index 0000000..eff3e2d
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/GenerateDocumentConsumer.cs
@@ -0,0 +1,366 @@
+using Azure.Storage.Blobs.Models;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace Raports.Application.Consumers;
+
+internal class GenerateDocumentConsumer(ILogger logger,
+ IPublishEndpoint publish,
+ BlobContainerClient container,
+ RaportsDBContext database) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation("GenerateDocumentConsumer: generating document for RaportID={RaportId}", context.Message.Raport.ID);
+
+ var ct = context.CancellationToken;
+
+ QuestPDF.Settings.License = LicenseType.Community;
+
+ async Task PublishRaportFailedAsync(DefaultRaportDTO raportDto, int raportId, string description, CancellationToken cancellationToken)
+ {
+ try
+ {
+ logger.LogWarning("GenerateDocumentConsumer: publishing RaportFailed for Raport {RaportId}: {Description}", raportId, description);
+ var message = new RaportFailed()
+ {
+ FailedDate = DateTime.UtcNow,
+ Description = description,
+ Raport = raportDto
+ };
+
+ await publish.Publish(message, cancellationToken);
+ logger.LogInformation("GenerateDocumentConsumer: RaportFailed published for Raport {RaportId}", raportId);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "GenerateDocumentConsumer: error while publishing RaportFailed for Raport {RaportId}", raportId);
+ }
+ }
+
+ try
+ {
+ var dbRaport = await database.Raports
+ .Include(x => x.Period)
+ .Include(x => x.Status)
+ .Include(x => x.MeasurementGroups).ThenInclude(mg => mg.Measurement)
+ .Include(x => x.MeasurementGroups).ThenInclude(mg => mg.LocationGroups).ThenInclude(lg => lg.Location)
+ .Include(x => x.MeasurementGroups).ThenInclude(mg => mg.LocationGroups).ThenInclude(lg => lg.SampleGroups)
+ .FirstOrDefaultAsync(x => x.ID == context.Message.Raport.ID, ct);
+
+ if (dbRaport is null)
+ {
+ logger.LogWarning("GenerateDocumentConsumer: Raport {RaportId} not found", context.Message.Raport.ID);
+ return;
+ }
+
+ // Generate PDF document
+ var document = QuestPDF.Fluent.Document.Create(doc =>
+ {
+ doc.Page(page =>
+ {
+ page.Size(PageSizes.A4);
+ page.Margin(15, QuestPDF.Infrastructure.Unit.Point);
+
+ page.Header().Element(c => ComposeHeader(c, dbRaport));
+
+ page.Footer()
+ .AlignCenter()
+ .Text(text =>
+ {
+ text.CurrentPageNumber();
+ text.Span(" / ");
+ text.TotalPages();
+ });
+
+ page.Content().Element(c => ComposeContent(c, dbRaport));
+ });
+ });
+
+ // Generate PDF to memory stream
+ using var pdfStream = new MemoryStream();
+ document.GeneratePdf(pdfStream);
+ pdfStream.Position = 0;
+
+ // Upload to blob storage with raport ID in the name
+ var blobName = $"Raport-{dbRaport.ID}-{dbRaport.StartDate:yyyy-MM-dd}-{DateTime.UtcNow:yyyy-MM-dd_HH-mm-ss}.pdf";
+ var blobClient = container.GetBlobClient(blobName);
+
+ // Set blob metadata tags for easy identification and filtering
+ var blobHttpHeaders = new BlobHttpHeaders
+ {
+ ContentType = "application/pdf"
+ };
+
+ var blobMetadata = new Dictionary
+ {
+ { "RaportID", dbRaport.ID.ToString() },
+ { "PeriodID", dbRaport.PeriodID.ToString() },
+ { "PeriodName", dbRaport.Period?.Name ?? "Unknown" },
+ { "StatusID", dbRaport.StatusID.ToString() },
+ { "StatusName", dbRaport.Status?.Name ?? "Unknown" },
+ { "StartDate", dbRaport.StartDate.ToString("yyyy-MM-dd") },
+ { "EndDate", dbRaport.EndDate.ToString("yyyy-MM-dd") },
+ { "RaportCreationDate", dbRaport.RaportCreationDate.ToString("yyyy-MM-dd_HH-mm-ss") },
+ { "RaportCompletedDate", dbRaport.RaportCompletedDate.ToString("yyyy-MM-dd_HH-mm-ss") },
+ { "DocumentGeneratedDate", DateTime.UtcNow.ToString("yyyy-MM-dd_HH-mm-ss") },
+ { "MeasurementGroupsCount", dbRaport.MeasurementGroups.Count.ToString() },
+ { "Version", "1.0" }
+ };
+
+ await blobClient.UploadAsync(
+ pdfStream,
+ new BlobUploadOptions
+ {
+ HttpHeaders = blobHttpHeaders,
+ Metadata = blobMetadata
+ },
+ cancellationToken: ct);
+
+ logger.LogInformation("GenerateDocumentConsumer: uploaded PDF '{BlobName}' for Raport {RaportId} with metadata", blobName, dbRaport.ID);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "GenerateDocumentConsumer: error while generating document for Raport {RaportId}", context.Message.Raport.ID);
+ await PublishRaportFailedAsync(context.Message.Raport, context.Message.Raport.ID, "Error while generating document: " + ex.Message, ct);
+ }
+ }
+
+ private void ComposeHeader(IContainer container, Raport raport)
+ {
+ container.Row(row =>
+ {
+ row.RelativeItem().Column(column =>
+ {
+ column.Item().Text("Homee System").AlignLeft().Bold().FontSize(16);
+ });
+
+ row.RelativeItem().Column(column =>
+ {
+ column.Item().Text($"{raport.Period?.Name ?? "Unknown"} Report").AlignCenter().Bold().FontSize(20);
+ });
+
+ row.RelativeItem().Column(column =>
+ {
+ column.Item().Text($"Date: {raport.StartDate:dd.MM.yyyy} - {raport.EndDate:dd.MM.yyyy}").AlignRight();
+ });
+ });
+ }
+
+ private void ComposeContent(IContainer container, Raport raport)
+ {
+ container.Column(column =>
+ {
+ column.Spacing(15);
+
+ // Add separator line after header with more space above, less below
+ column.Item().PaddingTop(8);
+ column.Item().LineHorizontal(1).LineColor("#000000");
+ column.Item().PaddingBottom(3);
+
+ // Title section with Raport ID prominently displayed
+ column.Item().Row(row =>
+ {
+ row.RelativeItem().Text($"Report ID: {raport.ID}").FontSize(14).Bold();
+ row.RelativeItem().Text($"Generated: {raport.RaportCreationDate:dd.MM.yyyy HH:mm}").FontSize(12).AlignRight();
+ });
+
+ column.Item().PaddingVertical(10);
+
+ // FIRST PAGE: Summary of included locations and measurements
+ column.Item().Text("Report Summary").FontSize(18).Bold();
+ column.Item().PaddingVertical(5);
+
+ // List of measurements included
+ column.Item().Text("Measurements Included:").FontSize(14).Bold();
+ column.Item().PaddingVertical(3);
+
+ foreach (var measurementGroup in raport.MeasurementGroups)
+ {
+ var measurementName = measurementGroup.Measurement?.Name ?? "Unknown Measurement";
+ var unit = measurementGroup.Measurement?.Unit ?? "";
+ column.Item().Text($"• {measurementName} ({unit})").FontSize(11);
+ }
+
+ column.Item().PaddingVertical(10);
+
+ // List of locations included
+ column.Item().Text("Locations Included:").FontSize(14).Bold();
+ column.Item().PaddingVertical(3);
+
+ var allLocations = raport.MeasurementGroups
+ .SelectMany(mg => mg.LocationGroups)
+ .Select(lg => lg.Location?.Name ?? "Unknown")
+ .Distinct()
+ .OrderBy(name => name)
+ .ToList();
+
+ foreach (var locationName in allLocations)
+ {
+ column.Item().Text($"• {locationName}").FontSize(11);
+ }
+
+ column.Item().PaddingVertical(10);
+
+ // Report period information
+ column.Item().Text("Report Period:").FontSize(14).Bold();
+ column.Item().PaddingVertical(3);
+ column.Item().Text($"From: {raport.StartDate:dd.MM.yyyy HH:mm}").FontSize(11);
+ column.Item().Text($"To: {raport.EndDate:dd.MM.yyyy HH:mm}").FontSize(11);
+
+ // START FROM SECOND PAGE: Charts and descriptions
+ // Iterate through measurement groups
+ foreach (var measurementGroup in raport.MeasurementGroups)
+ {
+ column.Item().PageBreak();
+
+ // Add separator line at top of each page with consistent spacing
+ column.Item().PaddingTop(8);
+ column.Item().LineHorizontal(1).LineColor("#000000");
+ column.Item().PaddingBottom(3);
+
+ var measurementName = measurementGroup.Measurement?.Name ?? "Unknown Measurement";
+
+ column.Item().Text(measurementName).AlignCenter().Bold().FontSize(18);
+ column.Item().PaddingVertical(5);
+
+ // Measurement group summary
+ if (!string.IsNullOrWhiteSpace(measurementGroup.Summary))
+ {
+ column.Item().Text(measurementGroup.Summary).Justify().FontSize(10);
+ column.Item().PaddingVertical(5);
+ }
+
+ // Single chart showing all locations for this measurement
+ if (measurementGroup.LocationGroups.Any(lg => lg.SampleGroups.Any()))
+ {
+ var chartSvg = ComposeMultiLocationChartImage(measurementGroup, raport);
+ if (!string.IsNullOrEmpty(chartSvg))
+ {
+ // Chart with title, labels, and legend all rendered by ScottPlot as SVG
+ column.Item().Svg(chartSvg).FitWidth();
+ }
+ }
+
+ column.Item().PaddingVertical(10);
+
+ // Location-specific summaries
+ column.Item().Column(summaryColumn =>
+ {
+ summaryColumn.Spacing(8);
+
+ foreach (var locationGroup in measurementGroup.LocationGroups)
+ {
+ var locationName = locationGroup.Location?.Name ?? "Unknown Location";
+
+ summaryColumn.Item().Row(summaryRow =>
+ {
+ summaryRow.RelativeItem().Column(locColumn =>
+ {
+ locColumn.Item().Text(locationName).Bold().FontSize(12);
+
+ if (!string.IsNullOrWhiteSpace(locationGroup.Summary))
+ {
+ locColumn.Item().Text(locationGroup.Summary).FontSize(9);
+ }
+ });
+ });
+ }
+ });
+ }
+ });
+ }
+
+ private string ComposeMultiLocationChartImage(MeasurementGroup measurementGroup, Raport raport)
+ {
+ try
+ {
+ var measurement = measurementGroup.Measurement;
+ if (measurement == null)
+ {
+ logger.LogWarning("Measurement is null in chart generation");
+ return string.Empty;
+ }
+
+ var locationGroups = measurementGroup.LocationGroups.Where(lg => lg.SampleGroups.Any()).ToList();
+ if (!locationGroups.Any())
+ {
+ logger.LogWarning("No location groups with samples found");
+ return string.Empty;
+ }
+
+ logger.LogInformation("Generating chart for {MeasurementName} with {LocationCount} locations",
+ measurement.Name, locationGroups.Count);
+
+ logger.LogInformation("Generating TEST chart with multi-language text");
+
+ ScottPlot.Plot myPlot = new();
+
+ myPlot.Font.Automatic(); // set font for each item based on its content
+
+ // Define colors
+ var colors = new List
+ {
+ ScottPlot.Colors.Blue, ScottPlot.Colors.Red, ScottPlot.Colors.Green,
+ ScottPlot.Colors.Orange, ScottPlot.Colors.Purple, ScottPlot.Colors.Cyan,
+ ScottPlot.Colors.Magenta, ScottPlot.Colors.Brown, ScottPlot.Colors.Pink,
+ ScottPlot.Colors.Lime, ScottPlot.Colors.Navy, ScottPlot.Colors.Teal,
+ ScottPlot.Colors.Maroon
+ };
+
+ int colorIndex = 0;
+
+ // Add data series
+ foreach (var locationGroup in locationGroups)
+ {
+ var samples = locationGroup.SampleGroups.OrderBy(s => s.Date).ToList();
+ if (!samples.Any()) continue;
+
+ var dates = samples.Select(s => s.Date).ToArray();
+ var values = samples.Select(s => s.Value).ToArray();
+
+ var scatter = myPlot.Add.Scatter(dates, values);
+ scatter.LegendText = locationGroup.Location?.Name ?? "Unknown";
+ scatter.Color = colors[colorIndex % colors.Count];
+ scatter.LineWidth = 3;
+ scatter.MarkerSize = 8;
+ scatter.MarkerShape = ScottPlot.MarkerShape.FilledCircle;
+
+ colorIndex++;
+ }
+
+ // Configure axes
+ myPlot.Axes.SetLimitsY(measurement.MinChartYValue, measurement.MaxChartYValue);
+ myPlot.Axes.SetLimitsX(raport.StartDate.ToOADate(), raport.EndDate.ToOADate());
+ myPlot.Axes.DateTimeTicksBottom();
+
+ // Set chart title and labels
+ myPlot.Title($"{measurement.Name} ({measurement.Unit})");
+ myPlot.XLabel("Time");
+ myPlot.YLabel(measurement.Unit);
+
+ // Show legend
+ myPlot.ShowLegend();
+
+ // Configure font sizes
+ myPlot.Axes.Title.Label.FontSize = 16;
+ myPlot.Axes.Bottom.Label.FontSize = 12;
+ myPlot.Axes.Left.Label.FontSize = 12;
+ myPlot.Axes.Bottom.TickLabelStyle.FontSize = 10;
+ myPlot.Axes.Left.TickLabelStyle.FontSize = 10;
+
+ // Generate SVG (vector format - scales perfectly, no font issues)
+ var svg = myPlot.GetSvgXml(600, 400);
+
+ logger.LogInformation("Generated TEST chart SVG with {Size} characters", svg.Length);
+
+ return svg;
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Error generating chart image");
+ return string.Empty;
+ }
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/GenerateSummaryConsumer.cs b/Services/Raports/Raports.Application/Consumers/GenerateSummaryConsumer.cs
new file mode 100644
index 0000000..e132852
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/GenerateSummaryConsumer.cs
@@ -0,0 +1,200 @@
+using OpenAI.Chat;
+using Raports.DataTransferObjects.RaportSummaryContainers;
+
+namespace Raports.Application.Consumers;
+
+internal class GenerateSummaryConsumer(ILogger logger,
+ IPublishEndpoint publish,
+ ChatClient openAiClient,
+ RaportsDBContext database) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation("GenerateSummaryConsumer: generating summaries for RaportID={RaportId}", context.Message.Raport.ID);
+
+ var ct = context.CancellationToken;
+
+ var dbRaport = await database.Raports
+ .Include(x => x.RequestedMeasurements)
+ .Include(x => x.RequestedLocations)
+ .Include(x => x.MeasurementGroups)
+ .ThenInclude(y => y.LocationGroups)
+ .ThenInclude(z => z.SampleGroups)
+ .Include(x => x.MeasurementGroups).ThenInclude(mg => mg.Measurement)
+ .Include(x => x.MeasurementGroups).ThenInclude(mg => mg.LocationGroups).ThenInclude(lg => lg.Location)
+ .Include(x => x.Period)
+ .FirstOrDefaultAsync(x => x.ID == context.Message.Raport.ID, ct);
+
+ if (dbRaport is null)
+ {
+ logger.LogWarning("GenerateSummaryConsumer: Raport {RaportId} not found", context.Message.Raport.ID);
+ return;
+ }
+
+ const string lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
+
+ var modified = false;
+
+
+ var messages = new ChatMessage[]
+ {
+ "I have data of measurements (for example temperature) that was captured for variuls locations and for various measurement types. Your job is to create short summary of this data." +
+ "Data that i will provide for you will have its period, for example 'Daily' or 'Weekly', you should generate simmary of its data accordingly to its type. " +
+ "Dataset will have measurement groups, each measurement group represent single type of measrueent, for example 'Temperature'. One measurement group can have multiple 'Location Gropus'" +
+ "Each location gropu represent a location where this measruement was captured. ",
+ };
+
+
+ foreach (var mg in dbRaport.MeasurementGroups)
+ {
+ // First, generate summaries for all locations in this measurement group
+ var locationSummaries = new List();
+
+ foreach (var lg in mg.LocationGroups)
+ {
+ if (!string.IsNullOrWhiteSpace(lg.Summary))
+ {
+ logger.LogInformation("Summary already exists for LocationGroup ID {LGId}, reusing", lg.ID);
+ locationSummaries.Add($"{lg.Location?.Name}: {lg.Summary}");
+ continue;
+ }
+
+ var samplesText = lg.SampleGroups?.Any() == true
+ ? string.Join(", ", lg.SampleGroups.Select(s => $"Time: {s.Date:yyyy-MM-dd HH:mm}, Value: {s.Value}"))
+ : "No samples";
+
+ var locGroupMessage = new LocationGroupDescription(
+ lg.Location.Name,
+ mg.Measurement.Name,
+ mg.Measurement.Unit,
+ mg.Raport.Period.Name,
+ samplesText
+ );
+
+ var locationAnalysisMessage = new ChatMessage[]
+ {
+ new SystemChatMessage(
+ "You are a data analyst specializing in home automation measurements. " +
+ "Your job is to analyze measurement data and provide concise summaries. " +
+ "Output ONLY the summary text, no introductory phrases, no extra formatting."
+ ),
+ new UserChatMessage(
+ "Analyze the following measurement data and provide a brief summary (2-3 sentences):\n\n" +
+ $"{locGroupMessage}"
+ )
+ };
+
+ try
+ {
+ var response = await openAiClient.CompleteChatAsync(locationAnalysisMessage, cancellationToken: ct);
+ var summary = response.Value.Content[0].Text?.Trim();
+
+ if (!string.IsNullOrWhiteSpace(summary))
+ {
+ lg.Summary = summary;
+ locationSummaries.Add($"{lg.Location?.Name}: {summary}");
+ modified = true;
+ logger.LogInformation("Generated AI summary for LocationGroup ID {LGId} (Location: {Location}): {Summary}",
+ lg.ID, lg.Location?.Name, summary.Substring(0, Math.Min(50, summary.Length)) + "...");
+ }
+ else
+ {
+ lg.Summary = lorem;
+ locationSummaries.Add($"{lg.Location?.Name}: {lorem}");
+ modified = true;
+ logger.LogWarning("Empty AI response for LocationGroup ID {LGId}, using placeholder", lg.ID);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Error generating AI summary for LocationGroup ID {LGId}, using placeholder", lg.ID);
+ lg.Summary = lorem;
+ locationSummaries.Add($"{lg.Location?.Name}: {lorem}");
+ modified = true;
+ }
+ }
+
+ // Now generate the overall measurement summary from all location summaries
+ if (string.IsNullOrWhiteSpace(mg.Summary) && locationSummaries.Any())
+ {
+ var combinedLocationSummaries = string.Join("\n\n", locationSummaries);
+
+ var measurementAnalysisMessage = new ChatMessage[]
+ {
+ new SystemChatMessage(
+ "You are a data analyst specializing in home automation measurements. " +
+ "Your job is to analyze summaries from multiple locations and create a comprehensive overview. " +
+ "Output ONLY the summary text, no introductory phrases, no extra formatting."
+ ),
+ new UserChatMessage(
+ $"I have {mg.Measurement?.Name} ({mg.Measurement?.Unit}) measurements from multiple locations during a {mg.Raport.Period?.Name} period.\n\n" +
+ $"Here are the individual location summaries:\n\n{combinedLocationSummaries}\n\n" +
+ $"Create a comprehensive summary (3-4 sentences) that analyzes the overall {mg.Measurement?.Name} patterns across all locations, " +
+ $"highlighting key trends, comparisons between locations, and any notable observations."
+ )
+ };
+
+ try
+ {
+ var response = await openAiClient.CompleteChatAsync(measurementAnalysisMessage, cancellationToken: ct);
+ var summary = response.Value.Content[0].Text?.Trim();
+
+ if (!string.IsNullOrWhiteSpace(summary))
+ {
+ mg.Summary = summary;
+ modified = true;
+ logger.LogInformation("Generated AI summary for MeasurementGroup ID {MGId} (Measurement: {Measurement}): {Summary}",
+ mg.ID, mg.Measurement?.Name, summary.Substring(0, Math.Min(50, summary.Length)) + "...");
+ }
+ else
+ {
+ mg.Summary = lorem;
+ modified = true;
+ logger.LogWarning("Empty AI response for MeasurementGroup ID {MGId}, using placeholder", mg.ID);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Error generating AI summary for MeasurementGroup ID {MGId}, using placeholder", mg.ID);
+ mg.Summary = lorem;
+ modified = true;
+ }
+ }
+ else if (!string.IsNullOrWhiteSpace(mg.Summary))
+ {
+ logger.LogInformation("Summary already exists for MeasurementGroup ID {MGId}, skipping", mg.ID);
+ }
+ else
+ {
+ logger.LogWarning("No location summaries available for MeasurementGroup ID {MGId}, using placeholder", mg.ID);
+ mg.Summary = lorem;
+ modified = true;
+ }
+ }
+
+ if (modified)
+ {
+ try
+ {
+ await database.SaveChangesAsync(ct);
+ logger.LogInformation("GenerateSummaryConsumer: saved summaries for Raport {RaportId}", dbRaport.ID);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "GenerateSummaryConsumer: error while saving summaries for Raport {RaportId}", dbRaport.ID);
+ throw;
+ }
+ }
+ else
+ {
+ logger.LogInformation("GenerateSummaryConsumer: no summaries needed for Raport {RaportId}", dbRaport.ID);
+ }
+
+ var message = new GenerateDocument()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, ct);
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/Pending/ProcessDailyRaportConsumer.cs b/Services/Raports/Raports.Application/Consumers/Pending/ProcessDailyRaportConsumer.cs
new file mode 100644
index 0000000..8126aa7
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/Pending/ProcessDailyRaportConsumer.cs
@@ -0,0 +1,19 @@
+namespace Raports.Application.Consumers.Pending;
+
+internal class ProcessDailyRaportConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation($"Processing Daily raport");
+
+ var message = new RaportToSummary()
+ {
+ Raport = context.Message.Raport
+ };
+
+ await publish.Publish(message, context =>
+ {
+ context.Headers.Set("PeriodName", message.Raport.Period.Name);
+ });
+ }
+}
diff --git a/Services/Raports/Raports.Application/Consumers/Pending/ProcessHourlyRaportConsumer.cs b/Services/Raports/Raports.Application/Consumers/Pending/ProcessHourlyRaportConsumer.cs
new file mode 100644
index 0000000..889933d
--- /dev/null
+++ b/Services/Raports/Raports.Application/Consumers/Pending/ProcessHourlyRaportConsumer.cs
@@ -0,0 +1,482 @@
+using CommonServiceLibrary.GRPC.Types.Measurements;
+
+namespace Raports.Application.Consumers.Pending;
+
+internal class ProcessHourlyRaportConsumer(ILogger logger,
+ IPublishEndpoint publish,
+ RaportsDBContext database,
+ IMemoryCache cashe,
+ MeasurementService.MeasurementServiceClient measurementsGRPC,
+ DevicesService.DevicesServiceClient deviceGRPC) : IConsumer
+{
+ public async Task Consume(ConsumeContext context)
+ {
+ logger.LogInformation($"Processing Hourly raport");
+
+ var cache = cashe; // keep original parameter name but use a clearer local variable
+ var ct = context.CancellationToken;
+
+ // Fetch and cache LocationGRPC objects from Devices service for requested locations
+ var cashedLocations = new List