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(); + foreach (var requestedLocation in context.Message.Raport.RequestedLocations) + { + var key = $"{nameof(LocationGRPC)}:{requestedLocation.Hash}"; + + var cashedLocation = cache.Get(key); + if (cashedLocation is null) + { + try + { + var response = await deviceGRPC.GetLocationByNameAsync(new LocationByNameRequest() { Name = requestedLocation.Name }).ResponseAsync.WaitAsync(ct); + var castedResponse = response.Adapt(); + + cache.Set(key, castedResponse); + cashedLocation = castedResponse; + } + catch (OperationCanceledException) + { + logger.LogInformation("Location lookup canceled for {Location}", requestedLocation.Name); + throw; + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to fetch location '{Location}' from Devices service; skipping location", requestedLocation.Name); + continue; + } + } + + cashedLocations.Add(cashedLocation); + } + + if (cashedLocations.Count == 0) + { + logger.LogInformation("No locations to query measurements for; aborting."); + return; + } + + // Get Measurements from Measurements service via gRPC + var grpcMeasurements = new List(); + var request = new MeasurementsQueryRequest() + { + SortOrder = "asc", + StartDate = context.Message.Raport.StartDate.ToString("o"), + EndDate = context.Message.Raport.EndDate.ToString("o"), + }; + + foreach (var loc in cashedLocations) + { + request.LocationIds.Add(loc.Hash.ToString() ?? string.Empty); + } + + try + { + using (var call = measurementsGRPC.MeasurementsQuery(request)) + { + while (await call.ResponseStream.MoveNext(ct)) + { + var current = call.ResponseStream.Current; + + if (!Guid.TryParse(current.Id, out var id)) + { + logger.LogWarning("Skipping measurement with invalid Id: {Id}", current.Id); + continue; + } + + if (!Guid.TryParse(current.DeviceNumber, out var deviceNumber)) + { + logger.LogWarning("Skipping measurement with invalid DeviceNumber: {DeviceNumber}", current.DeviceNumber); + continue; + } + + if (!DateTime.TryParse(current.MeasurementCaptureDate, out var measurementCaptureDate)) + { + logger.LogWarning("Skipping measurement with invalid MeasurementCaptureDate: {Date}", current.MeasurementCaptureDate); + continue; + } + + if (!Guid.TryParse(current.LocationHash, out var locationHash)) + { + logger.LogWarning("Skipping measurement with invalid LocationHash: {LocationHash}", current.LocationHash); + continue; + } + + var mes = new MeasurementGRPC( + id, + deviceNumber, + measurementCaptureDate, + locationHash, + + current.Temperature == double.MinValue ? null : current.Temperature, + current.Humidity == double.MinValue ? null : current.Humidity, + current.Co2 == double.MinValue ? null : current.Co2, + current.Voc == double.MinValue ? null : current.Voc, + current.ParticulateMatter1 == double.MinValue ? null : current.ParticulateMatter1, + current.ParticulateMatter2V5 == double.MinValue ? null : current.ParticulateMatter2V5, + current.ParticulateMatter10 == double.MinValue ? null : current.ParticulateMatter10, + current.Formaldehyde == double.MinValue ? null : current.Formaldehyde, + current.Co == double.MinValue ? null : current.Co, + current.O3 == double.MinValue ? null : current.O3, + current.Ammonia == double.MinValue ? null : current.Ammonia, + current.Airflow == double.MinValue ? null : current.Airflow, + current.AirIonizationLevel == double.MinValue ? null : current.AirIonizationLevel, + current.O2 == double.MinValue ? null : current.O2, + current.Radon == double.MinValue ? null : current.Radon, + current.Illuminance == double.MinValue ? null : current.Illuminance, + current.SoundLevel == double.MinValue ? null : current.SoundLevel + ); + + grpcMeasurements.Add(mes); + } + } + } + catch (OperationCanceledException) + { + logger.LogInformation("Measurement streaming canceled"); + throw; + } + catch (Exception ex) + { + logger.LogError(ex, "Error while querying measurements"); + } + + // sort oldest to newest and group by location + var measurementsByLocation = grpcMeasurements + .OrderBy(m => m.MeasurementCaptureDate) + .GroupBy(m => m.LocationHash) + .ToDictionary(g => g.Key, g => g.ToList()); + + // Determine timeframe for buckets from the Raport Period + TimeSpan timeframe = TimeSpan.Zero; + try + { + timeframe = context.Message.Raport.Period?.TimeFrame ?? TimeSpan.Zero; + } + catch + { + timeframe = TimeSpan.Zero; + } + + if (timeframe <= TimeSpan.Zero) + { + logger.LogWarning("Period timeframe is not set or invalid. Falling back to 1 hour."); + timeframe = TimeSpan.FromHours(1); + } + + var start = context.Message.Raport.StartDate; + var end = context.Message.Raport.EndDate; + + // For each location, create centered windows for each timepoint from start..end (inclusive of end) + var groupedByLocationAndTime = new Dictionary Measurements)>>(); + + foreach (var loc in measurementsByLocation) + { + var buckets = new List<(DateTime TimePoint, DateTime WindowStart, DateTime WindowEnd, List)>(); + + // build timepoints: start, start+timeframe, ..., up to <= end. Always include end as last point. + var timepoints = new List(); + for (var tp = start; tp <= end; tp = tp.Add(timeframe)) + { + timepoints.Add(tp); + } + if (timepoints.Count == 0 || timepoints.Last() < end) + { + timepoints.Add(end); + } + + var half = TimeSpan.FromTicks((long)(timeframe.Ticks / 2.0)); + + foreach (var tp in timepoints) + { + var windowStart = tp - half; + var windowEnd = tp + half; + + // clamp to report range + if (windowStart < start) windowStart = start; + if (windowEnd > end) windowEnd = end; + + // include measurements: >= windowStart and < windowEnd, but include measurements equal to end for last bucket + List items; + if (windowEnd == end) + { + items = loc.Value.Where(m => m.MeasurementCaptureDate >= windowStart && m.MeasurementCaptureDate <= windowEnd).ToList(); + } + else + { + items = loc.Value.Where(m => m.MeasurementCaptureDate >= windowStart && m.MeasurementCaptureDate < windowEnd).ToList(); + } + + buckets.Add((tp, windowStart, windowEnd, items)); + } + + groupedByLocationAndTime[loc.Key] = buckets; + } + + // Aggregate averages per bucket excluding nulls + + var aggregatedByLocation = new Dictionary>(); + + foreach (var kv in groupedByLocationAndTime) + { + var list = new List(); + + foreach (var bucket in kv.Value) + { + var items = bucket.Measurements; + + double? Avg(IEnumerable seq) + { + var vals = seq.Where(x => x.HasValue).Select(x => x!.Value).ToList(); + return vals.Count == 0 ? null : (double?)vals.Average(); + } + + var agg = new AggregatedBucket( + bucket.TimePoint, + bucket.WindowStart, + bucket.WindowEnd, + Avg(items.Select(x => x.Temperature)), + Avg(items.Select(x => x.Humidity)), + Avg(items.Select(x => x.CarbonDioxide)), + Avg(items.Select(x => x.VolatileOrganicCompounds)), + Avg(items.Select(x => x.ParticulateMatter1)), + Avg(items.Select(x => x.ParticulateMatter2v5)), + Avg(items.Select(x => x.ParticulateMatter10)), + Avg(items.Select(x => x.Formaldehyde)), + Avg(items.Select(x => x.CarbonMonoxide)), + Avg(items.Select(x => x.Ozone)), + Avg(items.Select(x => x.Ammonia)), + Avg(items.Select(x => x.Airflow)), + Avg(items.Select(x => x.AirIonizationLevel)), + Avg(items.Select(x => x.Oxygen)), + Avg(items.Select(x => x.Radon)), + Avg(items.Select(x => x.Illuminance)), + Avg(items.Select(x => x.SoundLevel)), + items.Count); + + list.Add(agg); + } + + aggregatedByLocation[kv.Key] = list; + } + + // Persist aggregated data into Raports DB + // create raport + var periodId = context.Message.Raport.Period?.ID ?? 0; + if (periodId == 0) + { + var period = await database.Periods.FirstOrDefaultAsync(p => p.TimeFrame == timeframe); + periodId = period?.ID ?? 1; + } + + var dbLocations = await database.Locations.ToListAsync(); + var status = await database.Statuses.FirstOrDefaultAsync(s => s.Name == "Completed") ?? await database.Statuses.FirstOrDefaultAsync(); + + var raport = await database.Raports.FirstOrDefaultAsync(x => x.ID == context.Message.Raport.ID); + if (raport is null) + { + // ? + throw new InvalidOperationException(); + } + + // prepare mapping and lookups + var measurementsLookup = await database.Measurements.ToListAsync(); + + var measurementMap = new Dictionary() + { + { "Temperature", "Air Temperature" }, + { "Humidity", "Relative Humidity" }, + { "CarbonDioxide", "Carbon Dioxide" }, + { "VolatileOrganicCompounds", "Volatile Organic Compounds" }, + { "ParticulateMatter1", "Particulate Matter 1um" }, + { "ParticulateMatter2v5", "Particulate Matter 2.5um" }, + { "ParticulateMatter10", "Particulate Matter 10um" }, + { "Formaldehyde", "Formaldehyde" }, + { "CarbonMonoxide", "Carbon Monoxide" }, + { "Ozone", "Ozone" }, + { "Ammonia", "Ammonia" }, + { "Airflow", "Air Flow Rate" }, + { "AirIonizationLevel", "Air Ionization Level" }, + { "Oxygen", "Oxygen Concentration" }, + { "Radon", "Radon Concentration" }, + { "Illuminance", "Illuminance level" }, + { "SoundLevel", "Sound Pressure Level" } + }; + + foreach (var mapEntry in measurementMap) + { + var key = mapEntry.Key; // property name on AggregatedBucket + var measurementName = mapEntry.Value; + + var measurementEntity = measurementsLookup.FirstOrDefault(m => m.Name == measurementName); + if (measurementEntity is null) + { + logger.LogWarning($"Measurement with name '{measurementName}' was not recognized!"); + continue; + } + + var isMeasurementTypeRequested = context.Message.Raport.RequestedMeasurements.Any(x => x.Name == measurementName); + if (isMeasurementTypeRequested == false) + { + logger.LogWarning($"Measurement '{measurementEntity.Name}' was not requested for this raport, skipping."); + continue; + } + else + { + logger.LogWarning($"Creating Measurement Group for '{measurementEntity.Name}'"); + } + + var measurementGroup = new MeasurementGroup + { + MeasurementID = measurementEntity.ID, + RaportID = raport.ID, + Summary = string.Empty + }; + + await database.MeasurementGroups.AddAsync(measurementGroup); + await database.SaveChangesAsync(); + + // for each location store location group + samples + foreach (var locEntry in aggregatedByLocation) + { + var locHash = locEntry.Key; + var dbLocation = await database.Locations.FirstOrDefaultAsync(l => l.Hash == locHash); + + if (dbLocation is null) + { + logger.LogWarning($"Location with hash '{locHash}' was not recognized!"); + continue; + } + + var isLocationRequested = context.Message.Raport.RequestedLocations.Any(x => x.Hash == locHash); + if (isLocationRequested == false) + { + logger.LogWarning($"Location '{dbLocation.Name}' was not requested for this raport, skipping.", locHash); + continue; + } + else + { + logger.LogWarning($"Creating Location Group for '{dbLocation.Name}'", locHash); + } + + // Append new location group to DB! + var locationGroup = new LocationGroup + { + LocationID = dbLocation.ID, + MeasurementGroupID = measurementGroup.ID, + Summary = string.Empty + }; + + await database.LocationGroups.AddAsync(locationGroup); + await database.SaveChangesAsync(); + + // Create samples + var samples = new List(); + foreach (var bucket in locEntry.Value) + { + var val = GetMeasurementValueByKey(bucket, key); + if (val.HasValue) + { + samples.Add(new SampleGroup { Date = bucket.TimePoint, Value = val.Value, LocationGroupID = locationGroup.ID }); + } + } + + if (samples.Count > 0) + { + await database.SampleGroups.AddRangeAsync(samples); + await database.SaveChangesAsync(); + } + } + } + + logger.LogInformation($"Hourly raport processed and saved RaportID={raport.ID}"); + + // Forward to next step + var validateRaportMessage = new ValidateRaport() + { + Raport = context.Message.Raport + }; + + await publish.Publish(validateRaportMessage, ct); + + logger.LogInformation($"Raport sent to validation!"); + } + + private static double? GetMeasurementValueByKey(AggregatedBucket bucket, string key) + { + return key switch + { + "Temperature" => bucket.Temperature, + "Humidity" => bucket.Humidity, + "CarbonDioxide" => bucket.CarbonDioxide, + "VolatileOrganicCompounds" => bucket.VolatileOrganicCompounds, + "ParticulateMatter1" => bucket.ParticulateMatter1, + "ParticulateMatter2v5" => bucket.ParticulateMatter2v5, + "ParticulateMatter10" => bucket.ParticulateMatter10, + "Formaldehyde" => bucket.Formaldehyde, + "CarbonMonoxide" => bucket.CarbonMonoxide, + "Ozone" => bucket.Ozone, + "Ammonia" => bucket.Ammonia, + "Airflow" => bucket.Airflow, + "AirIonizationLevel" => bucket.AirIonizationLevel, + "Oxygen" => bucket.Oxygen, + "Radon" => bucket.Radon, + "Illuminance" => bucket.Illuminance, + "SoundLevel" => bucket.SoundLevel, + _ => null + }; + } + + private sealed class AggregatedBucket + { + public DateTime TimePoint { get; } + public DateTime WindowStart { get; } + public DateTime WindowEnd { get; } + public double? Temperature { get; } + public double? Humidity { get; } + public double? CarbonDioxide { get; } + public double? VolatileOrganicCompounds { get; } + public double? ParticulateMatter1 { get; } + public double? ParticulateMatter2v5 { get; } + public double? ParticulateMatter10 { get; } + public double? Formaldehyde { get; } + public double? CarbonMonoxide { get; } + public double? Ozone { get; } + public double? Ammonia { get; } + public double? Airflow { get; } + public double? AirIonizationLevel { get; } + public double? Oxygen { get; } + public double? Radon { get; } + public double? Illuminance { get; } + public double? SoundLevel { get; } + public int Count { get; } + + public AggregatedBucket(DateTime timePoint, DateTime windowStart, DateTime windowEnd, + 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, int count) + { + TimePoint = timePoint; + WindowStart = windowStart; + WindowEnd = windowEnd; + Temperature = temperature; + Humidity = humidity; + CarbonDioxide = carbonDioxide; + VolatileOrganicCompounds = volatileOrganicCompounds; + ParticulateMatter1 = particulateMatter1; + ParticulateMatter2v5 = particulateMatter2v5; + ParticulateMatter10 = particulateMatter10; + Formaldehyde = formaldehyde; + CarbonMonoxide = carbonMonoxide; + Ozone = ozone; + Ammonia = ammonia; + Airflow = airflow; + AirIonizationLevel = airIonizationLevel; + Oxygen = oxygen; + Radon = radon; + Illuminance = illuminance; + SoundLevel = soundLevel; + Count = count; + } + } +} diff --git a/Services/Raports/Raports.Application/Consumers/Pending/ProcessMonthlyRaportConsumer.cs b/Services/Raports/Raports.Application/Consumers/Pending/ProcessMonthlyRaportConsumer.cs new file mode 100644 index 0000000..d1a0498 --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/Pending/ProcessMonthlyRaportConsumer.cs @@ -0,0 +1,19 @@ +namespace Raports.Application.Consumers.Pending; + +internal class ProcessMonthlyRaportConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Processing Monthly 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/ProcessWeeklyRaportConsumer.cs b/Services/Raports/Raports.Application/Consumers/Pending/ProcessWeeklyRaportConsumer.cs new file mode 100644 index 0000000..9df4351 --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/Pending/ProcessWeeklyRaportConsumer.cs @@ -0,0 +1,19 @@ +namespace Raports.Application.Consumers.Pending; + +internal class ProcessWeeklyRaportConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Processing Weekly 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/ProcessRaportReady.cs b/Services/Raports/Raports.Application/Consumers/ProcessRaportReady.cs new file mode 100644 index 0000000..edf34ee --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/ProcessRaportReady.cs @@ -0,0 +1,9 @@ +namespace Raports.Application.Consumers; + +internal class ProcessRaportReady(ILogger logger) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Raport ready"); + } +} diff --git a/Services/Raports/Raports.Application/Consumers/RaportFailedConsumer.cs b/Services/Raports/Raports.Application/Consumers/RaportFailedConsumer.cs new file mode 100644 index 0000000..9f09960 --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/RaportFailedConsumer.cs @@ -0,0 +1,58 @@ +namespace Raports.Application.Consumers; + +internal class RaportFailedConsumer(ILogger logger, + IPublishEndpoint publish, + RaportsDBContext database, + IMemoryCache cashe, + MeasurementService.MeasurementServiceClient measurementsGRPC, + DevicesService.DevicesServiceClient deviceGRPC) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation("RaportFailedConsumer: handling failure for RaportID={RaportId}", context.Message.Raport?.ID); + + var ct = context.CancellationToken; + + if (context.Message is null) + { + logger.LogWarning("Received empty RaportFailed message"); + return; + } + + var raportDto = context.Message.Raport; + if (raportDto is null) + { + logger.LogWarning("RaportFailed message does not contain Raport DTO"); + return; + } + + try + { + var raport = await database.Raports.FirstOrDefaultAsync(r => r.ID == raportDto.ID, ct); + if (raport is null) + { + logger.LogWarning("Raport with ID {RaportId} not found in DB", raportDto.ID); + return; + } + + var failedStatus = await database.Statuses.FirstOrDefaultAsync(s => s.Name == "Failed", ct) ?? await database.Statuses.FirstOrDefaultAsync(ct); + if (failedStatus is not null) + { + raport.StatusID = failedStatus.ID; + } + + // set message and completion date from incoming message + raport.Message = context.Message.Description ?? raport.Message; + raport.RaportCompletedDate = context.Message.FailedDate == default ? DateTime.UtcNow : context.Message.FailedDate; + + await database.SaveChangesAsync(ct); + + logger.LogInformation("Raport ID {RaportId} updated to Failed in DB", raport.ID); + } + catch (Exception ex) + { + logger.LogError(ex, "Error while handling RaportFailed for RaportID={RaportId}", context.Message.Raport?.ID); + throw; + } + } +} diff --git a/Services/Raports/Raports.Application/Consumers/Summary/ProcessDailySummaryConsumer.cs b/Services/Raports/Raports.Application/Consumers/Summary/ProcessDailySummaryConsumer.cs new file mode 100644 index 0000000..8ac6a55 --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/Summary/ProcessDailySummaryConsumer.cs @@ -0,0 +1,19 @@ +namespace Raports.Application.Consumers.Summary; + +internal class ProcessDailySummaryConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Generating Daily summary"); + + var message = new RaportProduceDocument() + { + 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/Summary/ProcessHourlySummaryConsumer.cs b/Services/Raports/Raports.Application/Consumers/Summary/ProcessHourlySummaryConsumer.cs new file mode 100644 index 0000000..6bd356e --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/Summary/ProcessHourlySummaryConsumer.cs @@ -0,0 +1,19 @@ +namespace Raports.Application.Consumers.Summary; + +internal class ProcessHourlySummaryConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Generating Hourly summary"); + + var message = new RaportProduceDocument() + { + 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/Summary/ProcessMonthlySummaryConsumer.cs b/Services/Raports/Raports.Application/Consumers/Summary/ProcessMonthlySummaryConsumer.cs new file mode 100644 index 0000000..31f2c0e --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/Summary/ProcessMonthlySummaryConsumer.cs @@ -0,0 +1,19 @@ +namespace Raports.Application.Consumers.Summary; + +internal class ProcessMonthlySummaryConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Generating Monthly summary"); + + var message = new RaportProduceDocument() + { + 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/Summary/ProcessWeeklySummaryConsumer.cs b/Services/Raports/Raports.Application/Consumers/Summary/ProcessWeeklySummaryConsumer.cs new file mode 100644 index 0000000..af1c0eb --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/Summary/ProcessWeeklySummaryConsumer.cs @@ -0,0 +1,19 @@ +namespace Raports.Application.Consumers.Summary; + +internal class ProcessWeeklySummaryConsumer(ILogger logger, IPublishEndpoint publish) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"Generating Weekly summary"); + + var message = new RaportProduceDocument() + { + 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/ValidateRaportConsumer.cs b/Services/Raports/Raports.Application/Consumers/ValidateRaportConsumer.cs new file mode 100644 index 0000000..0cca325 --- /dev/null +++ b/Services/Raports/Raports.Application/Consumers/ValidateRaportConsumer.cs @@ -0,0 +1,139 @@ +namespace Raports.Application.Consumers; + +internal class ValidateRaportConsumer(ILogger logger, + IPublishEndpoint publish, + RaportsDBContext database) : IConsumer +{ + public async Task Consume(ConsumeContext context) + { + logger.LogInformation($"ValidateRaportConsumer: validating RaportID={context.Message.Raport.ID}"); + + 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.Period) + .FirstOrDefaultAsync(x => x.ID == context.Message.Raport.ID); + + var ct = context.CancellationToken; + + if (dbRaport is null) + { + var desc = $"Raport {context.Message.Raport.ID} not found in DB"; + + logger.LogWarning(desc); + await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, desc, ct); + + return; + } + + // quick access + var requestedLocations = context.Message.Raport.RequestedLocations; + var requestedMeasurements = context.Message.Raport.RequestedMeasurements; + + // 1) Check whether report has all of requested measurements and locations + foreach (var reqMes in requestedMeasurements) + { + var mesGroup = dbRaport.MeasurementGroups.FirstOrDefault(x => x.Measurement != null && x.Measurement.Name == reqMes.Name); + if (mesGroup is null) + { + var desc = $"Requested measurement '{reqMes.Name}' not present in Raport ID {dbRaport.ID}"; + await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, desc, ct); + return; + } + + foreach (var reqLoc in requestedLocations) + { + var hasLocationGroup = mesGroup.LocationGroups.Any(x => x.Location != null && x.Location.Name == reqLoc.Name); + if (!hasLocationGroup) + { + var desc = $"Measurement '{reqMes.Name}' missing location '{reqLoc.Name}' in Raport ID {dbRaport.ID}"; + await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, desc, ct); + return; + } + } + } + + // 2) Now check missing samples per measurement/location against Period.MaxAcceptableMissingTimeFrame + var period = dbRaport.Period ?? await database.Periods.FirstOrDefaultAsync(p => p.ID == dbRaport.PeriodID); + var timeframe = period?.TimeFrame ?? TimeSpan.FromHours(1); + if (timeframe <= TimeSpan.Zero) timeframe = TimeSpan.FromHours(1); + + var maxMissing = period?.MaxAcceptableMissingTimeFrame ?? 0; + + 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); + + // For each measurement group and its location groups, count missing sample points + foreach (var measurementGroup in dbRaport.MeasurementGroups) + { + var measurementName = measurementGroup.Measurement?.Name ?? ""; + + foreach (var locationGroup in measurementGroup.LocationGroups) + { + var locationName = locationGroup.Location?.Name ?? ""; + + // Build set of sample dates present (normalize to canonical points by exact match) + var sampleDates = new HashSet(locationGroup.SampleGroups.Select(s => s.Date)); + + if (sampleDates.Count == 0) + { + var desc = $"Measurement '{measurementName}' was not captured at location '{locationName}'"; + await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, desc, ct); + return; + } + + int missingCount = canonicalTimepoints.Count(tp => !sampleDates.Contains(tp)); + + if (missingCount > maxMissing) + { + var desc = $"Measurement '{measurementName}' at location '{locationName}' missing {missingCount} samples (allowed {maxMissing}) in Raport ID {dbRaport.ID}"; + await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, desc, ct); + return; + } + } + } + + try + { + await publish.Publish(new AdjustRaport { Raport = context.Message.Raport }, ct); + } + catch (Exception ex) + { + string desc = "Error while publishing RaportPending for Raport {dbRaport.ID}"; + logger.LogError(ex, desc); + await PublishRaportFailedAsync(context.Message.Raport, dbRaport.ID, desc, ct); + } + } + + private async Task PublishRaportFailedAsync(DefaultRaportDTO raportDto, int raportId, string description, CancellationToken ct) + { + try + { + logger.LogWarning("Validation failed for Raport {RaportId}: {Description}", raportId, description); + + var message = new RaportFailed() + { + FailedDate = DateTime.UtcNow, + Description = description, + Raport = raportDto + }; + + await publish.Publish(message, ct); + + logger.LogInformation("RaportFailed published for Raport {RaportId}", raportId); + } + catch (Exception ex) + { + logger.LogError(ex, "Error while publishing RaportFailed for Raport {RaportId}", raportId); + throw; + } + } +} diff --git a/Services/Raports/Raports.Application/DependencyInjection.cs b/Services/Raports/Raports.Application/DependencyInjection.cs index d2f1816..1b51d8a 100644 --- a/Services/Raports/Raports.Application/DependencyInjection.cs +++ b/Services/Raports/Raports.Application/DependencyInjection.cs @@ -1,59 +1,267 @@ -namespace Raports.Application +using CommonServiceLibrary.GRPC; + +namespace Raports.Application; + +public static class DependencyInjection { - public static class DependencyInjection + public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment environment) { - public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration) + services.AddCarter(); + + services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + + services.AddMediatR(x => { - services.AddCarter(); + x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); + x.AddOpenBehavior(typeof(ValidationBehavior<,>)); + x.AddOpenBehavior(typeof(LoggingBehavior<,>)); + }); - services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); + services.AddGRPCMappings(); + services.AddGrpcClient(options => + { + string grpcConnectionString = string.Empty; + if (environment.IsDevelopment()) + { + grpcConnectionString = configuration.GetConnectionString("MeasurementsGRPC_Dev"); + } + else if (environment.IsStaging()) + { + grpcConnectionString = configuration.GetConnectionString("MeasurementsGRPC_Dev"); + } + else + { + grpcConnectionString = configuration.GetConnectionString("MeasurementsGRPC_Prod"); + } - services.AddMediatR(x => + options.Address = new Uri(grpcConnectionString); + }) + .ConfigurePrimaryHttpMessageHandler(() => + { + return new HttpClientHandler { - x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); - x.AddOpenBehavior(typeof(ValidationBehavior<,>)); - x.AddOpenBehavior(typeof(LoggingBehavior<,>)); - }); + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; + }); + + services.AddGrpcClient(options => + { + 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(() => + { + return new HttpClientHandler + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + }; + }); + + services.AddMemoryCache(); - var config = new TypeAdapterConfig(); + var config = new TypeAdapterConfig(); - config.Apply(new PeriodMapper()); - config.Apply(new RequestsStatusMapper()); - config.Apply(new RequestMapper()); - config.Apply(new RaportMapper()); + config.Apply(new PeriodMapper()); + config.Apply(new StatusMapper()); + config.Apply(new MeasurementMapper()); + config.Apply(new LocationMapper()); + config.Apply(new RaportMapper()); - services.AddSingleton(config); + services.AddSingleton(config); - services.AddScoped(); - services.AddScoped(); + // MassTransit - Azure Service Bus + services.AddMassTransit(config => + { + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); + config.AddConsumer(); - // MassTransit - Azure Service Bus - services.AddMassTransit(config => + config.UsingAzureServiceBus((context, configurator) => { - config.SetKebabCaseEndpointNameFormatter(); + configurator.Host(configuration.GetConnectionString("AzureServiceBus")); + + // Map message types to existing topics + if (environment.IsProduction()) + { + // Producer/Consumer + configurator.Message(x => x.SetEntityName("homee.raportsdocumenttopic.prod")); + configurator.Message(x => x.SetEntityName("homee.raportspendingtopic.prod")); + configurator.Message(x => x.SetEntityName("homee.raportssummarytopic.prod")); + configurator.Message(x => x.SetEntityName("homee.raportsgeneratedtopic.prod")); + configurator.Message(x => x.SetEntityName("homee.raportsvalidatedata.prod")); + configurator.Message(x => x.SetEntityName("homee.raportfailed.prod")); + configurator.Message(x => x.SetEntityName("homee.raportsadjust.prod")); + configurator.Message(x => x.SetEntityName("homee.raportsgeneratesummary.prod")); + configurator.Message(x => x.SetEntityName("homee.raportsgeneratedocument.prod")); + } + else + { + // Producer/Consumer + configurator.Message(x => x.SetEntityName("homee.raportsdocumenttopic.dev")); + configurator.Message(x => x.SetEntityName("homee.raportspendingtopic.dev")); + configurator.Message(x => x.SetEntityName("homee.raportssummarytopic.dev")); + configurator.Message(x => x.SetEntityName("homee.raportsgeneratedtopic.dev")); + configurator.Message(x => x.SetEntityName("homee.raportsvalidatedata.dev")); + configurator.Message(x => x.SetEntityName("homee.raportfailed.dev")); + configurator.Message(x => x.SetEntityName("homee.raportsadjust.dev")); + configurator.Message(x => x.SetEntityName("homee.raportsgeneratesummary.dev")); + configurator.Message(x => x.SetEntityName("homee.raportsgeneratedocument.dev")); + } + + // Subscriptions + // Generate document + configurator.SubscriptionEndpoint("raports-generate-document-subscription", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Summary + configurator.SubscriptionEndpoint("raports-generate-summary-subscription", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Adjust + configurator.SubscriptionEndpoint("raports-adjust-subscription", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Failed + configurator.SubscriptionEndpoint("raports-failed-subscription", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Validate + configurator.SubscriptionEndpoint("raports-validate-data-subscription", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Pending + configurator.SubscriptionEndpoint("raports-process-hourly-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-process-daily-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-process-weekly-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-process-monthly-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Generate document + configurator.SubscriptionEndpoint("raports-hourly-document-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-daily-document-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-weekly-document-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-monthly-document-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); - config.UsingAzureServiceBus((context, configurator) => + // Summary + configurator.SubscriptionEndpoint("raports-hourly-summary-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-daily-summary-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-weekly-summary-raport", e => { - configurator.Host(configuration.GetConnectionString("AzureServiceBus")); + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + configurator.SubscriptionEndpoint("raports-monthly-summary-raport", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; + }); + + // Raport ready + configurator.SubscriptionEndpoint("raports-raport-ready", e => + { + e.ConfigureConsumer(context); + e.ConfigureConsumeTopology = false; }); }); + }); - services.AddExceptionHandler(); + services.AddExceptionHandler(); - services.AddSignalR(); + services.AddSignalR(); - return services; - } + return services; + } - public static WebApplication UseApplicationServices(this WebApplication app) - { - app.MapCarter(); + public static WebApplication UseApplicationServices(this WebApplication app) + { + app.MapCarter(); - app.MapHub("/raportshub"); + app.MapHub("/raportshub"); - app.UseExceptionHandler(x => { }); + app.UseExceptionHandler(x => { }); - return app; - } + return app; } } diff --git a/Services/Raports/Raports.Application/Fonts/LiberationSans-Regular.ttf b/Services/Raports/Raports.Application/Fonts/LiberationSans-Regular.ttf new file mode 100644 index 0000000..e633985 Binary files /dev/null and b/Services/Raports/Raports.Application/Fonts/LiberationSans-Regular.ttf differ diff --git a/Services/Raports/Raports.Application/GlobalUsings.cs b/Services/Raports/Raports.Application/GlobalUsings.cs index 6e032c4..349beae 100644 --- a/Services/Raports/Raports.Application/GlobalUsings.cs +++ b/Services/Raports/Raports.Application/GlobalUsings.cs @@ -1,27 +1,39 @@ -global using Carter; +global using Azure.Storage.Blobs; +global using Carter; global using CommonServiceLibrary.Behaviors; global using CommonServiceLibrary.DataStructures; global using CommonServiceLibrary.Exceptions; global using CommonServiceLibrary.Exceptions.Handlers; -global using CommonServiceLibrary.Messaging; +global using CommonServiceLibrary.GRPC.Types.Devices; +global using Devices.GRPCClient; global using FluentValidation; global using Mapster; global using MassTransit; +global using Measurements.GRPCClient; global using MediatR; global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.Hosting; global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Routing; global using Microsoft.AspNetCore.SignalR; global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.Caching.Memory; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; +global using Raports.Application.Consumers; +global using Raports.Application.Consumers.Document; +global using Raports.Application.Consumers.Pending; +global using Raports.Application.Consumers.Summary; global using Raports.Application.Handlers.Create; global using Raports.Application.Handlers.Read; +global using Raports.Application.Handlers.Update; global using Raports.Application.Hubs; global using Raports.Application.Mappers; +global using Raports.Application.Messages; global using Raports.DataTransferObjects; global using Raports.Domain.Entities; global using Raports.Infrastructure.Database; -global using Raports.Infrastructure.Generators; global using System.Reflection; \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Create/CreateRaportCommand.cs b/Services/Raports/Raports.Application/Handlers/Create/CreateRaportCommand.cs new file mode 100644 index 0000000..34d44fe --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Create/CreateRaportCommand.cs @@ -0,0 +1,11 @@ +namespace Raports.Application.Handlers.Create; + +public record CreateRaportResponse(DefaultRaportDTO RaportDTO); +public record CreateRaportCommand(DateTime StartDate, DateTime EndDate, int PeriodID, int[] RequestedLocationsIDs, int[] RequestedMeasurementsIDs) : IRequest; +public class CreateRaportCommandValidator : AbstractValidator +{ + public CreateRaportCommandValidator() + { + + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Create/CreateRaportHandler.cs b/Services/Raports/Raports.Application/Handlers/Create/CreateRaportHandler.cs new file mode 100644 index 0000000..bd85df2 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Create/CreateRaportHandler.cs @@ -0,0 +1,106 @@ +namespace Raports.Application.Handlers.Create; + +public class CreateRaportHandler(RaportsDBContext database, IHubContext hub, IPublishEndpoint publishEndpoint) : IRequestHandler +{ + public async Task Handle(CreateRaportCommand request, CancellationToken cancellationToken) + { + var foundPeriod = await database.Periods.FirstOrDefaultAsync(x => x.ID == request.PeriodID, cancellationToken); + if (foundPeriod is null) + { + throw new EntityNotFoundException(nameof(Period), request.PeriodID); + } + + var foundLocations = new List(); + foreach (var reqLocID in request.RequestedLocationsIDs) + { + var foundLocation = await database.Locations.FirstOrDefaultAsync(x => x.ID == reqLocID, cancellationToken); + if (foundLocation is null) + { + throw new EntityNotFoundException(nameof(Location), reqLocID); + } + + foundLocations.Add(foundLocation); + } + + var foundMeasurements = new List(); + foreach (var reqMesID in request.RequestedMeasurementsIDs) + { + var foundMeasurement = await database.Measurements.FirstOrDefaultAsync(x => x.ID == reqMesID, cancellationToken); + if (foundMeasurement is null) + { + throw new EntityNotFoundException(nameof(Measurement), reqMesID); + } + + foundMeasurements.Add(foundMeasurement); + } + + var pendingStatus = await database.Statuses.FirstOrDefaultAsync(x => x.Name == "Pending"); + if (pendingStatus is null) + { + throw new EntityNotFoundException(nameof(Status), "Pending"); + } + + var newRaport = new Raport() + { + RaportCreationDate = DateTime.UtcNow, + StartDate = request.StartDate, + EndDate = request.EndDate, + Message = string.Empty, + Period = foundPeriod, + Status = pendingStatus, + }; + + // Create new Raport + await database.Raports.AddAsync(newRaport, cancellationToken); + await database.SaveChangesAsync(); + + // Create RequestedMeasruement for that Raport + foreach (var reqMes in foundMeasurements) + { + var newRequestedMeasurement = new RequestedMeasurement() + { + Raport = newRaport, + Measurement = reqMes + }; + + await database.RequestedMeasurements.AddAsync(newRequestedMeasurement, cancellationToken); + } + + // Create RequestedLocation for that Raport + foreach (var reqLoc in foundLocations) + { + var newRequestedLocation = new RequestedLocation() + { + Raport = newRaport, + Location = reqLoc + }; + + await database.RequestedLocations.AddAsync(newRequestedLocation, cancellationToken); + } + + await database.SaveChangesAsync(); + + // Get refreshed Raport + newRaport = await database.Raports + .Include(x => x.RequestedMeasurements) + .Include(x => x.RequestedLocations) + .Include(x => x.Period) + .FirstOrDefaultAsync(x => x.ID == newRaport.ID); + + var dto = newRaport.Adapt(); + var response = new CreateRaportResponse(dto); + + await hub.Clients.All.SendAsync("RaportCreated", dto, cancellationToken); + + var message = new RaportPending() + { + Raport = dto + }; + await publishEndpoint.Publish(message, context => + { + context.Headers.Set("PeriodName", message.Raport.Period.Name); + }); + + return response; + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Create/CreateRequestCommand.cs b/Services/Raports/Raports.Application/Handlers/Create/CreateRequestCommand.cs deleted file mode 100644 index ee1c2af..0000000 --- a/Services/Raports/Raports.Application/Handlers/Create/CreateRequestCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Raports.Application.Handlers.Create; - -public record CreateRequestCommand(CreateRequestDTO CreateDTO) : IRequest; -public class CreateRequestCommandValidator : AbstractValidator -{ - public CreateRequestCommandValidator() { } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Create/CreateRequestHandler.cs b/Services/Raports/Raports.Application/Handlers/Create/CreateRequestHandler.cs deleted file mode 100644 index 7baa512..0000000 --- a/Services/Raports/Raports.Application/Handlers/Create/CreateRequestHandler.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace Raports.Application.Handlers.Create; - -public class CreateRequestHandler(RaportsDBContext dBContext, IHubContext hub) : IRequestHandler -{ - public async Task Handle(CreateRequestCommand request, CancellationToken cancellationToken) - { - var createDTO = request.CreateDTO; - - var periodEntity = await dBContext.Periods.FirstOrDefaultAsync(x => x.Name == createDTO.PeriodName, cancellationToken); - if (periodEntity is null) - { - throw new EntityNotFoundException(nameof(Period), createDTO.PeriodName); - } - - var statusEntity = await dBContext.RequestStatuses.FirstOrDefaultAsync(x => x.Name == "Pending", cancellationToken); - if (statusEntity is null) - { - throw new EntityNotFoundException(nameof(RequestStatus), "Pending"); - } - - // Remove everyting after hours. - DateTime endDate = new DateTime( - createDTO.EndDate.Year, - createDTO.EndDate.Month, - createDTO.EndDate.Day, - createDTO.EndDate.Hour, - 0, - 0); - - DateTime startDate = new DateTime( - createDTO.StartDate.Year, - createDTO.StartDate.Month, - createDTO.StartDate.Day, - createDTO.StartDate.Hour, - 0, - 0); - - - var requestEntity = await dBContext.Requests - .Include(x => x.Period) - .FirstOrDefaultAsync(x => x.StartDate == createDTO.StartDate && x.EndDate == createDTO.EndDate && x.Period.Name == createDTO.PeriodName); - if (requestEntity is not null) - { - throw new DuplicateEntityException(nameof(Request), request.CreateDTO); - } - - Request newReqeust = new Request() - { - RequestCreationDate = DateTime.Now, - StartDate = startDate, - EndDate = endDate, - PeriodID = periodEntity.ID, - StatusID = statusEntity.ID - }; - - await dBContext.Requests.AddAsync(newReqeust, cancellationToken); - await dBContext.SaveChangesAsync(); - - var requestDTO = newReqeust.Adapt(); - - var response = new ReadRequestResponse(requestDTO); - - await hub.Clients.All.SendAsync("RequestCreated", requestDTO, cancellationToken); - - return response; - } -} diff --git a/Services/Raports/Raports.Application/Handlers/Delete/DeleteRequestHandler.cs b/Services/Raports/Raports.Application/Handlers/Delete/DeleteRequestHandler.cs deleted file mode 100644 index 481267f..0000000 --- a/Services/Raports/Raports.Application/Handlers/Delete/DeleteRequestHandler.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Raports.Application.Handlers.Delete; - -public class DeleteRequestHandler(RaportsDBContext dbcontext, IHubContext hub) : IRequestHandler -{ - public async Task Handle(SoftDeleteRequestCommand request, CancellationToken cancellationToken) - { - var requestEntity = await dbcontext.Requests - .Include(x => x.Status) - .Include(x => x.Period) - .Include(x => x.Raport) - .FirstOrDefaultAsync(x => x.ID == request.RequestID); - if (requestEntity is null) - { - throw new EntityNotFoundException(nameof(Request), request.RequestID); - } - - var deleteStatusEntity = await dbcontext.RequestStatuses.FirstOrDefaultAsync(x => x.Name == "Deleted"); - if (deleteStatusEntity is null) - { - throw new EntityNotFoundException(nameof(RequestStatus), "Deleted"); - } - - requestEntity.StatusID = deleteStatusEntity.ID; - - var entry = dbcontext.Entry(requestEntity); - dbcontext.ChangeTracker.DetectChanges(); - - var wasChanged = entry.Properties.Any(p => p.IsModified) || entry.ComplexProperties.Any(c => c.IsModified); - - var requestDTO = requestEntity.Adapt(); - var response = new ReadRequestResponse(requestDTO); - - if (wasChanged) - { - await dbcontext.SaveChangesAsync(cancellationToken); - await hub.Clients.All.SendAsync("RequestStatusChanged", requestDTO, cancellationToken); - } - - return response; - } -} diff --git a/Services/Raports/Raports.Application/Handlers/Delete/SoftDeleteRequestCommand.cs b/Services/Raports/Raports.Application/Handlers/Delete/SoftDeleteRequestCommand.cs deleted file mode 100644 index bba1684..0000000 --- a/Services/Raports/Raports.Application/Handlers/Delete/SoftDeleteRequestCommand.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Raports.Application.Handlers.Delete; - -public record SoftDeleteRequestCommand(int RequestID) : IRequest; -public class SoftDeleteRequestCommandValidator : AbstractValidator -{ - public SoftDeleteRequestCommandValidator() { } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/RaportsServiceEndpoints.cs b/Services/Raports/Raports.Application/Handlers/RaportsServiceEndpoints.cs index ab392d4..c8fe1e0 100644 --- a/Services/Raports/Raports.Application/Handlers/RaportsServiceEndpoints.cs +++ b/Services/Raports/Raports.Application/Handlers/RaportsServiceEndpoints.cs @@ -1,141 +1,93 @@ -using Raports.Application.Handlers.Delete; -using Raports.Application.Handlers.Update; - -namespace Raports.Application.Handlers; +namespace Raports.Application.Handlers; public class RaportsServiceEndpoints : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { - // Requests - app.MapPost("/raports/requests", async (ISender sender, CreateRequestDTO createRequestDTO) => - { - var response = await sender.Send(new CreateRequestCommand(createRequestDTO)); + // ---------- Post ---------- - var dto = response.RequestDTO; - - return Results.Ok(dto); - }); - - app.MapPut("/raports/requests/update/{ID}", async (ISender sender, int ID, UpdateRequestDTO updateDTO) => + app.MapPost("/raports/raport", async (ISender sender, [FromQuery] DateTime StartDate, [FromQuery] DateTime EndDate, [FromQuery] int PeriodID, [FromQuery] int[] RequestedLocationsIDs, [FromQuery] int[] RequestedMeasurementsIDs) => { - var response = await sender.Send(new UpdateRequestCommand(ID, updateDTO)); - - var dto = response.RequestDTO; + var response = await sender.Send(new CreateRaportCommand(StartDate, EndDate, PeriodID, RequestedLocationsIDs, RequestedMeasurementsIDs)); + var dto = response.RaportDTO; return Results.Ok(dto); }); - app.MapPut("/raports/requests/changestatus/{ID}", async (ISender sender, int ID, UpdateRequestStatusDTO updateDTO) => - { - var response = await sender.Send(new UpdateRequestStatusCommand(ID, updateDTO)); + // ---------- Get All ---------- - var dto = response.RequestDTO; + app.MapGet("/raports/locations/all", async (ISender sender) => + { + var response = await sender.Send(new ReadAllLocationsCommand()); + var dto = response.LocationsDTOs; return Results.Ok(dto); }); - app.MapPut("/raports/requests/updateraport/{ID}", async (ISender sender, int ID, UpdateRequestRaportDTO updateDTO) => - { - var response = await sender.Send(new UpdateRequestRaportCommand(ID, updateDTO)); - var dto = response.RequestDTO; + app.MapGet("/raports/measurements/all", async (ISender sender) => + { + var response = await sender.Send(new ReadAllMeasurementsCommand()); + var dto = response.MeasurementDTOs; return Results.Ok(dto); }); - app.MapPut("/raports/requests/delete/{ID}", async (ISender sender, int ID) => + app.MapGet("/raports/periods/all", async (ISender sender) => { - var response = await sender.Send(new SoftDeleteRequestCommand(ID)); - - var dto = response.RequestDTO; + var response = await sender.Send(new ReadAllPeriodCommand()); + var dto = response.PeriodsDTO; return Results.Ok(dto); }); - app.MapGet("/raports/requests/{ID}", async (ISender sender, int ID) => + app.MapGet("/raports/statuses/all", async (ISender sender) => { - var response = await sender.Send(new ReadRequestCommand(ID)); - - var dto = response.RequestDTO; + var response = await sender.Send(new ReadAllStatusesCommand()); + var dto = response.StatusesDTOs; return Results.Ok(dto); }); - app.MapGet("/raports/requests/all", async (ISender sender) => + app.MapGet("/raports/raports/all", async (ISender sender) => { - var response = await sender.Send(new ReadAllRequestCommand()); - - var dto = response.RequestsDTOs; + var response = await sender.Send(new ReadAllRaportsCommand()); + var dto = response.RaportDTOs; return Results.Ok(dto); }); - app.MapGet("/raports/requests/query", async (ISender sender, DateTime? CreationDateFrom, DateTime? CreationDateTo, string? SortOrder, string? PeriodName, string? StatusName, int Page, int PageSize) => + app.MapGet("/raports/query", async (ISender sender, [FromQuery] DateTime? RaportCreationDateFrom, [FromQuery] DateTime? RaportCreationDateTo, [FromQuery] string? SortOrder, [FromQuery] string? PeriodName, [FromQuery] string? StatusName, [FromQuery] int Page, [FromQuery] int PageSize) => { - var response = await sender.Send(new ReadAllRequestsQueryCommand(CreationDateFrom, CreationDateTo, SortOrder, PeriodName, StatusName, Page, PageSize)); + var response = await sender.Send(new ReadAllRaportsQueryCommand(RaportCreationDateFrom, RaportCreationDateTo, SortOrder, PeriodName, StatusName, Page, PageSize)); return Results.Ok(response); }); - // Raports - app.MapDelete("/raports/raports", async (ISender sender) => - { - Console.WriteLine("Delete"); - }); + // ---------- Get ---------- app.MapGet("/raports/raports/{ID}", async (ISender sender, int ID) => { var response = await sender.Send(new ReadRaportCommand(ID)); - var dto = response.RaportDTO; return Results.Ok(dto); }); - app.MapGet("/raports/raports/all", async (ISender sender) => - { - var response = await sender.Send(new ReadAllRaportsCommand()); - - var dto = response.RaportDTOs; + // ---------- Put ---------- - return Results.Ok(dto); - }); - - // Statuses - app.MapGet("/raports/statuses/{ID}", async (ISender sender, int ID) => - { - var response = await sender.Send(new ReadRaportStatusesCommand(ID)); - - var dto = response.RequestStatusDTO; - - return Results.Ok(dto); - }); - - app.MapGet("/raports/statuses/all", async (ISender sender) => + app.MapPut("/raports/raport/status", async ([FromQuery] int RaportID, [FromQuery] int StatusID, ISender sender) => { - var response = await sender.Send(new ReadAllRaportStatusesCommand()); - - var dto = response.RequestStatusesDTOs; - - return Results.Ok(dto); - }); - - // Periods - app.MapGet("/raports/periods/{ID}", async (ISender sender, int ID) => - { - var response = await sender.Send(new ReadPeriodCommand(ID)); - - var dto = response.PeriodDTO; + var response = await sender.Send(new UpdateRaportStatusCommand(RaportID, StatusID)); + var dto = response.RaportDTO; return Results.Ok(dto); }); - app.MapGet("/raports/periods/all", async (ISender sender) => + app.MapPut("/raports/raport", async ([FromQuery] int RaportID, [FromQuery] DateTime? StartDate, [FromQuery] DateTime? EndDate, [FromQuery] int? PeriodID, ISender sender) => { - var response = await sender.Send(new ReadAllPeriodCommand()); - - var dto = response.PeriodsDTOs; + var response = await sender.Send(new UpdateRaportCommand(RaportID, StartDate, EndDate, PeriodID)); + var dto = response.RaportDTO; return Results.Ok(dto); }); diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadLocationCommand.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadLocationCommand.cs new file mode 100644 index 0000000..0fb32c5 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadLocationCommand.cs @@ -0,0 +1,8 @@ +namespace Raports.Application.Handlers.Read; + +public record ReadAllLocationsResponse(IEnumerable LocationsDTOs); +public record ReadAllLocationsCommand() : IRequest; +public class ReadAllLocationsCommandValidator : AbstractValidator +{ + public ReadAllLocationsCommandValidator() { } +} diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadLocationHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadLocationHandler.cs new file mode 100644 index 0000000..6492105 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadLocationHandler.cs @@ -0,0 +1,15 @@ +namespace Raports.Application.Handlers.Read; + +public class ReadAllLocationsHandler(RaportsDBContext database) : IRequestHandler +{ + public async Task Handle(ReadAllLocationsCommand request, CancellationToken cancellationToken) + { + var locations = await database.Locations.ToListAsync(cancellationToken); + + var dtos = locations.Adapt>(); + + var response = new ReadAllLocationsResponse(dtos); + + return response; + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadMeasurementCommand.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadMeasurementCommand.cs new file mode 100644 index 0000000..d9486b2 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadMeasurementCommand.cs @@ -0,0 +1,8 @@ +namespace Raports.Application.Handlers.Read; + +public record ReadAllMeasurementsResponse(IEnumerable MeasurementDTOs); +public record ReadAllMeasurementsCommand() : IRequest; +public class ReadAllMeasurementsCommandValidator : AbstractValidator +{ + public ReadAllMeasurementsCommandValidator() { } +} diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadMeasurementHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadMeasurementHandler.cs new file mode 100644 index 0000000..7c8e2e2 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadMeasurementHandler.cs @@ -0,0 +1,15 @@ +namespace Raports.Application.Handlers.Read; + +public class ReadAllMeasurementsHandler(RaportsDBContext database) : IRequestHandler +{ + public async Task Handle(ReadAllMeasurementsCommand request, CancellationToken cancellationToken) + { + var measurements = await database.Measurements.ToListAsync(cancellationToken); + + var dtos = measurements.Adapt>(); + + var response = new ReadAllMeasurementsResponse(dtos); + + return response; + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodCommand.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodCommand.cs index 4e704ba..a7f9e80 100644 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodCommand.cs +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodCommand.cs @@ -1,15 +1,8 @@ namespace Raports.Application.Handlers.Read; -public record ReadPeriodCommand(int ID) : IRequest; -public record ReadPeriodResponse(DefaultPeriodDTO PeriodDTO); -public class ReadPeriodCommandValidator : AbstractValidator -{ - public ReadPeriodCommandValidator() { } -} - +public record ReadAllPeriodResponse(IEnumerable PeriodsDTO); public record ReadAllPeriodCommand() : IRequest; -public record ReadAllPeriodResponse(IEnumerable PeriodsDTOs); -public class ReadAllPeriodCommandValidator : AbstractValidator +public class ReadAllPeriodCommandValidator : AbstractValidator { public ReadAllPeriodCommandValidator() { } -} \ No newline at end of file +} diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodHandler.cs index e93c8e4..68c8b90 100644 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodHandler.cs +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadPeriodHandler.cs @@ -1,34 +1,14 @@ namespace Raports.Application.Handlers.Read; -public class ReadPeriodHandler(RaportsDBContext dBContext) : IRequestHandler -{ - public async Task Handle(ReadPeriodCommand request, CancellationToken cancellationToken) - { - var entity = await dBContext.Periods - .FirstOrDefaultAsync(x => x.ID == request.ID, cancellationToken: cancellationToken); - if (entity == null) - { - throw new EntityNotFoundException(nameof(Raport), request.ID); - } - - var dto = entity.Adapt(); - - var response = new ReadPeriodResponse(dto); - - return response; - } -} - -public class ReadAllPeriodHandler(RaportsDBContext dBContext) : IRequestHandler +public class ReadAllPeriodHandler(RaportsDBContext database) : IRequestHandler { public async Task Handle(ReadAllPeriodCommand request, CancellationToken cancellationToken) { - var entity = await dBContext.Periods - .ToListAsync(cancellationToken); + var periods = await database.Periods.ToListAsync(cancellationToken); - var dto = entity.Adapt>(); + var dtos = periods.Adapt>(); - var response = new ReadAllPeriodResponse(dto); + var response = new ReadAllPeriodResponse(dtos); return response; } diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportComands.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadRaportComands.cs index 6912c90..8e15c17 100644 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportComands.cs +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadRaportComands.cs @@ -13,3 +13,13 @@ public class ReadAllRaportsCommandValidator : AbstractValidator; +public record ReadAllRaportsQueryResponse(PaginatedList PaginatedList); +public class ReadAllRaportsQueryCommandValidator : AbstractValidator +{ + public ReadAllRaportsQueryCommandValidator() + { + + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadRaportHandler.cs index 2752bee..3d2274b 100644 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportHandler.cs +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadRaportHandler.cs @@ -5,8 +5,8 @@ public class ReadRaportHandler(RaportsDBContext dBContext) : IRequestHandler Handle(ReadRaportCommand request, CancellationToken cancellationToken) { var entity = await dBContext.Raports - .Include(x => x.Request) .Include(x => x.Period) + .Include(x => x.Status) .FirstOrDefaultAsync(x => x.ID == request.ID, cancellationToken: cancellationToken); if (entity == null) { @@ -26,14 +26,71 @@ public class ReadAllRaportHandler(RaportsDBContext dBContext) : IRequestHandler< public async Task Handle(ReadAllRaportsCommand request, CancellationToken cancellationToken) { var entity = await dBContext.Raports - .Include(x => x.Request) .Include(x => x.Period) + .Include(x => x.Status) .ToListAsync(cancellationToken); var dto = entity.Adapt>(); var response = new ReadAllRaportsResponse(dto); + return response; + } +} + + +public class ReadAllRaportsQueryHandler(RaportsDBContext dbcontext) : IRequestHandler +{ + public async Task Handle(ReadAllRaportsQueryCommand request, CancellationToken cancellationToken) + { + var query = dbcontext.Raports + .Include(x => x.Status) + .Include(x => x.Period) + .AsQueryable(); + + if (request.PeriodName is not null) + { + query = query.Where(x => x.Period.Name == request.PeriodName); + } + + if (request.StatusName is not null) + { + query = query.Where(x => x.Status.Name == request.StatusName); + } + + if (request.CreationDateFrom is not null) + { + query = query.Where(x => x.RaportCreationDate >= request.CreationDateFrom); + } + + if (request.CreationDateTo is not null) + { + query = query.Where(x => x.RaportCreationDate <= request.CreationDateTo); + } + + string sortOrder = "asc"; + if (!string.IsNullOrEmpty(request.SortOrder)) + { + sortOrder = request.SortOrder; + } + + if (sortOrder == "asc") + { + query = query.OrderBy(x => x.RaportCreationDate); + } + else + { + query = query.OrderByDescending(x => x.RaportCreationDate); + } + + var paginetedOutput = await PaginatedList.Create(query, request.Page, request.PageSize, await dbcontext.Raports.CountAsync()); + + var requestsDto = paginetedOutput.Items.Adapt>(); + + var paginatedDto = new PaginatedList(requestsDto, paginetedOutput.Page, paginetedOutput.PageSize, paginetedOutput.TotalCount, paginetedOutput.AbsoluteCount); + + var response = new ReadAllRaportsQueryResponse(paginatedDto); + return response; } } \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportStatusesCommands.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadRaportStatusesCommands.cs deleted file mode 100644 index 0ae45ed..0000000 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportStatusesCommands.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Raports.Application.Handlers.Read; - -public record ReadRaportStatusesCommand(int ID) : IRequest; -public record ReadRaportStatusesResponse(DefaultRequestStatusDTO RequestStatusDTO); -public class ReadRaportStatusesCommandValidator : AbstractValidator -{ - public ReadRaportStatusesCommandValidator() { } -} - -public record ReadAllRaportStatusesCommand() : IRequest; -public record ReadAllRaportStatusesResponse(IEnumerable RequestStatusesDTOs); -public class ReadAllRaportStatusesCommandValidator : AbstractValidator -{ - public ReadAllRaportStatusesCommandValidator() { } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportStatusesHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadRaportStatusesHandler.cs deleted file mode 100644 index 3fce6e9..0000000 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadRaportStatusesHandler.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Raports.Application.Handlers.Read; - -public class ReadRaportStatusHandler(RaportsDBContext dBContext) : IRequestHandler -{ - public async Task Handle(ReadRaportStatusesCommand request, CancellationToken cancellationToken) - { - var entity = await dBContext.RequestStatuses - .FirstOrDefaultAsync(x => x.ID == request.ID, cancellationToken: cancellationToken); - if (entity == null) - { - throw new EntityNotFoundException(nameof(Raport), request.ID); - } - - var dto = entity.Adapt(); - - var response = new ReadRaportStatusesResponse(dto); - - return response; - } -} - -public class ReadAllRaportsStatusesHandler(RaportsDBContext dBContext) : IRequestHandler -{ - public async Task Handle(ReadAllRaportStatusesCommand request, CancellationToken cancellationToken) - { - var entity = await dBContext.RequestStatuses - .ToListAsync(cancellationToken); - - var dto = entity.Adapt>(); - - var response = new ReadAllRaportStatusesResponse(dto); - - return response; - } -} diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadRequestCommand.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadRequestCommand.cs deleted file mode 100644 index c0c8f37..0000000 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadRequestCommand.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Raports.Application.Handlers.Read; - -public record ReadRequestCommand(int ID) : IRequest; -public record ReadRequestResponse(DefaultRequestDTO RequestDTO); -public class ReadRequestCommandValidator : AbstractValidator -{ - public ReadRequestCommandValidator() { } -} - -public record ReadAllRequestCommand() : IRequest; -public record ReadAllRequestResponse(IEnumerable RequestsDTOs); -public class ReadAllRequestCommandValidator : AbstractValidator -{ - public ReadAllRequestCommandValidator() { } -} - -public record ReadAllRequestsQueryCommand( - DateTime? CreationDateFrom, - DateTime? CreationDateTo, - string? SortOrder, - string? PeriodName, - string? StatusName, - int Page, - int PageSize) : IRequest>; -public class ReadAllRequestsQueryCommandValidator : AbstractValidator -{ - public ReadAllRequestsQueryCommandValidator() - { - - } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadRequestHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadRequestHandler.cs deleted file mode 100644 index 5898c47..0000000 --- a/Services/Raports/Raports.Application/Handlers/Read/ReadRequestHandler.cs +++ /dev/null @@ -1,99 +0,0 @@ -namespace Raports.Application.Handlers.Read; - -public class ReadRequestHandler(RaportsDBContext dBContext) : IRequestHandler -{ - public async Task Handle(ReadRequestCommand request, CancellationToken cancellationToken) - { - var entity = await dBContext.Requests - .Include(x => x.Period) - .Include(x => x.Raport) - .Include(x => x.Status) - .FirstOrDefaultAsync(x => x.ID == request.ID, cancellationToken: cancellationToken); - //if (entity == null || entity.Status.Name == "Deleted") - if (entity == null) - { - throw new EntityNotFoundException(nameof(Raport), request.ID); - } - - var dto = entity.Adapt(); - - var response = new ReadRequestResponse(dto); - - return response; - } -} - -public class ReadAllRequestHandler(RaportsDBContext dBContext) : IRequestHandler -{ - public async Task Handle(ReadAllRequestCommand request, CancellationToken cancellationToken) - { - var entity = await dBContext.Requests - .Include(x => x.Period) - .Include(x => x.Raport) - .Include(x => x.Status) - //.Where(x => x.Status.Name != "Deleted") - .ToListAsync(cancellationToken); - - var dto = entity.Adapt>(); - - var response = new ReadAllRequestResponse(dto); - - return response; - } -} - -public class ReadAllRequstsQueryHandler(RaportsDBContext dbcontext) : IRequestHandler> -{ - public async Task> Handle(ReadAllRequestsQueryCommand request, CancellationToken cancellationToken) - { - var query = dbcontext.Requests - .Include(x => x.Status) - .Include(x => x.Period) - .Include(x => x.Raport) - //.Where(x => x.Status.Name != "Deleted") - .AsQueryable(); - - if (request.PeriodName is not null) - { - query = query.Where(x => x.Period.Name == request.PeriodName); - } - - if (request.StatusName is not null) - { - query = query.Where(x => x.Status.Name == request.StatusName); - } - - if (request.CreationDateFrom is not null) - { - query = query.Where(x => x.RequestCreationDate >= request.CreationDateFrom); - } - - if (request.CreationDateTo is not null) - { - query = query.Where(x => x.RequestCreationDate <= request.CreationDateTo); - } - - string sortOrder = "asc"; - if (!string.IsNullOrEmpty(request.SortOrder)) - { - sortOrder = request.SortOrder; - } - - if (sortOrder == "asc") - { - query = query.OrderBy(x => x.RequestCreationDate); - } - else - { - query = query.OrderByDescending(x => x.RequestCreationDate); - } - - var paginetedOutput = await PaginatedList.Create(query, request.Page, request.PageSize, await dbcontext.Requests.CountAsync()); - - var requestsDto = paginetedOutput.Items.Adapt>(); - - var paginatedDto = new PaginatedList(requestsDto, paginetedOutput.Page, paginetedOutput.PageSize, paginetedOutput.TotalCount, paginetedOutput.AbsoluteCount); - - return paginatedDto; - } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadStatusCommands.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadStatusCommands.cs new file mode 100644 index 0000000..29e13a3 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadStatusCommands.cs @@ -0,0 +1,8 @@ +namespace Raports.Application.Handlers.Read; + +public record ReadAllStatusesCommand() : IRequest; +public record ReadAllStatusesResponse(IEnumerable StatusesDTOs); +public class ReadAllStatusesCommandValidator : AbstractValidator +{ + public ReadAllStatusesCommandValidator() { } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Read/ReadStatusHandler.cs b/Services/Raports/Raports.Application/Handlers/Read/ReadStatusHandler.cs new file mode 100644 index 0000000..3c87798 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Read/ReadStatusHandler.cs @@ -0,0 +1,16 @@ +namespace Raports.Application.Handlers.Read; + +public class ReadAllRaportsStatusesHandler(RaportsDBContext dBContext) : IRequestHandler +{ + public async Task Handle(ReadAllStatusesCommand request, CancellationToken cancellationToken) + { + var entity = await dBContext.Statuses + .ToListAsync(cancellationToken); + + var dto = entity.Adapt>(); + + var response = new ReadAllStatusesResponse(dto); + + return response; + } +} diff --git a/Services/Raports/Raports.Application/Handlers/Update/UpdateRaportStatusCommand.cs b/Services/Raports/Raports.Application/Handlers/Update/UpdateRaportStatusCommand.cs new file mode 100644 index 0000000..a409534 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Update/UpdateRaportStatusCommand.cs @@ -0,0 +1,34 @@ +namespace Raports.Application.Handlers.Update; + +public record UpdateRaportStatusCommand(int RaportID, int StatusID) : IRequest; +public class UpdateRaportStatusCommandValidator : AbstractValidator +{ + public UpdateRaportStatusCommandValidator() { } +} + +public record UpdateRaportCommand(int RaportID, DateTime? UpdatedStartDate, DateTime? UpdatedEndDate, int? UpdatedPeriodID) : IRequest; +public class UpdateRaportCommandValidator : AbstractValidator +{ + public UpdateRaportCommandValidator() + { + RuleFor(x => x) + .Must(x => + (!x.UpdatedStartDate.HasValue && !x.UpdatedEndDate.HasValue) || + (x.UpdatedStartDate.HasValue && x.UpdatedEndDate.HasValue)) + .WithMessage("Both UpdatedStartDate and UpdatedEndDate must be either null or defined."); + + RuleFor(x => x) + .Must(x => + !x.UpdatedStartDate.HasValue || + !x.UpdatedEndDate.HasValue || + x.UpdatedStartDate <= x.UpdatedEndDate) + .WithMessage("UpdatedStartDate must be earlier than or equal to UpdatedEndDate."); + + RuleFor(x => x) + .Must(x => + x.UpdatedStartDate.HasValue || + x.UpdatedEndDate.HasValue || + x.UpdatedPeriodID.HasValue) + .WithMessage("At least one field must be provided."); + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Update/UpdateRaportStatusHandler.cs b/Services/Raports/Raports.Application/Handlers/Update/UpdateRaportStatusHandler.cs new file mode 100644 index 0000000..0572351 --- /dev/null +++ b/Services/Raports/Raports.Application/Handlers/Update/UpdateRaportStatusHandler.cs @@ -0,0 +1,130 @@ +namespace Raports.Application.Handlers.Update; + +public class UpdateRaportStatusHandler(RaportsDBContext dbcontext, IHubContext hub) : IRequestHandler +{ + public async Task Handle(UpdateRaportStatusCommand request, CancellationToken cancellationToken) + { + var statusEntity = await dbcontext.Statuses.FirstOrDefaultAsync(x => x.ID == request.StatusID); + if (statusEntity is null) + { + throw new EntityNotFoundException(nameof(Status), request.RaportID); + } + + var requestEntity = await dbcontext.Raports + .Include(x => x.Status) + .Include(x => x.Period) + .FirstOrDefaultAsync(x => x.ID == request.RaportID); + if (requestEntity is null) + { + throw new EntityNotFoundException(nameof(Raport), request.RaportID); + } + + requestEntity.StatusID = statusEntity.ID; + + var entry = dbcontext.Entry(requestEntity); + dbcontext.ChangeTracker.DetectChanges(); + + var wasChanged = entry.Properties.Any(p => p.IsModified) || entry.ComplexProperties.Any(c => c.IsModified); + + var dto = requestEntity.Adapt(); + var response = new ReadRaportResponse(dto); + + if (wasChanged) + { + await dbcontext.SaveChangesAsync(cancellationToken); + await hub.Clients.All.SendAsync("RequestStatusChanged", dto, cancellationToken); + } + + return response; + } +} + +public class UpdateRaportHandler(RaportsDBContext dbcontext, IHubContext hub) : IRequestHandler +{ + public Task Handle(UpdateRaportCommand request, CancellationToken cancellationToken) + { + return null; + //Period? periodEntity = null; + + //var requestEntity = await dbcontext.Requests + // .Include(x => x.Status) + // .Include(x => x.Raport) + // .Include(x => x.Period) + // .FirstOrDefaultAsync(x => x.ID == request.RequestID); + //if (requestEntity is null) + //{ + // throw new EntityNotFoundException(nameof(Request), request.RequestID); + //} + + //if (requestEntity.RaportID == request.UpdateDTO.UpdatedRaportID) + //{ + // throw new Exception(); + //} + + //// Update raport + //if (request.UpdateDTO.UpdatedRaportID is not null) + //{ + // var raportEntity = await dbcontext.Raports.FirstOrDefaultAsync(x => x.ID == (int)request.UpdateDTO.UpdatedRaportID); + // if (raportEntity is null) + // { + // throw new EntityNotFoundException(nameof(Raport), request.UpdateDTO.UpdatedRaportID); + // } + + // requestEntity.RaportID = raportEntity.ID; + //} + + //// Update period + //if (request.UpdateDTO.UpdatedPeriodID is not null) + //{ + // periodEntity = await dbcontext.Periods.FirstOrDefaultAsync(x => x.ID == (int)request.UpdateDTO.UpdatedPeriodID); + // if (periodEntity is null) + // { + // throw new EntityNotFoundException(nameof(Period), request.UpdateDTO.UpdatedPeriodID); + // } + + // requestEntity.PeriodID = periodEntity.ID; + //} + + //if (request.UpdateDTO.UpdatedStartDate is not null && request.UpdateDTO.UpdatedEndDate is not null) + //{ + // DateTime start = (DateTime)request.UpdateDTO.UpdatedStartDate; + // DateTime end = (DateTime)request.UpdateDTO.UpdatedEndDate; + + // // Remove everyting after hours. + // DateTime endDate = new DateTime( + // end.Year, + // end.Month, + // end.Day, + // end.Hour, + // 0, + // 0); + + // DateTime startDate = new DateTime( + // start.Year, + // start.Month, + // start.Day, + // start.Hour, + // 0, + // 0); + + // requestEntity.StartDate = startDate; + // requestEntity.EndDate = endDate; + //} + + //var entry = dbcontext.Entry(requestEntity); + //dbcontext.ChangeTracker.DetectChanges(); + + //var wasChanged = entry.Properties.Any(p => p.IsModified) || entry.ComplexProperties.Any(c => c.IsModified); + + //var requestDTO = requestEntity.Adapt(); + //var response = new ReadRequestResponse(requestDTO); + + //if (wasChanged) + //{ + // await dbcontext.SaveChangesAsync(cancellationToken); + // await hub.Clients.All.SendAsync("RequestUpdated", requestDTO, cancellationToken); + //} + + //return response; + } +} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Update/UpdateRequestStatusCommand.cs b/Services/Raports/Raports.Application/Handlers/Update/UpdateRequestStatusCommand.cs deleted file mode 100644 index dea424a..0000000 --- a/Services/Raports/Raports.Application/Handlers/Update/UpdateRequestStatusCommand.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Raports.Application.Handlers.Update; - -public record UpdateRequestStatusCommand(int RequestID, UpdateRequestStatusDTO UpdateDTO) : IRequest; -public class UpdateRequestStatusCommandValidator : AbstractValidator -{ - public UpdateRequestStatusCommandValidator() { } -} - -public record UpdateRequestCommand(int RequestID, UpdateRequestDTO UpdateDTO) : IRequest; -public class UpdateRequestCommandValidator : AbstractValidator -{ - public UpdateRequestCommandValidator() - { - RuleFor(x => x) - .Must(x => - (!x.UpdateDTO.UpdatedStartDate.HasValue && !x.UpdateDTO.UpdatedEndDate.HasValue) || - (x.UpdateDTO.UpdatedStartDate.HasValue && x.UpdateDTO.UpdatedEndDate.HasValue)) - .WithMessage("Both UpdatedStartDate and UpdatedEndDate must be either null or defined."); - - RuleFor(x => x) - .Must(x => - !x.UpdateDTO.UpdatedStartDate.HasValue || - !x.UpdateDTO.UpdatedEndDate.HasValue || - x.UpdateDTO.UpdatedStartDate <= x.UpdateDTO.UpdatedEndDate) - .WithMessage("UpdatedStartDate must be earlier than or equal to UpdatedEndDate."); - - RuleFor(x => x) - .Must(x => - x.UpdateDTO.UpdatedStartDate.HasValue || - x.UpdateDTO.UpdatedEndDate.HasValue || - x.UpdateDTO.UpdatedPeriodID.HasValue || - x.UpdateDTO.UpdatedRaportID.HasValue) - .WithMessage("At least one field must be provided."); - } -} - -public record UpdateRequestRaportCommand(int RequestID, UpdateRequestRaportDTO UpdateDTO) : IRequest; -public class UpdateRequestRaportCommandValidator : AbstractValidator -{ - public UpdateRequestRaportCommandValidator() { } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Handlers/Update/UpdateRequestStatusHandler.cs b/Services/Raports/Raports.Application/Handlers/Update/UpdateRequestStatusHandler.cs deleted file mode 100644 index 7091014..0000000 --- a/Services/Raports/Raports.Application/Handlers/Update/UpdateRequestStatusHandler.cs +++ /dev/null @@ -1,170 +0,0 @@ -namespace Raports.Application.Handlers.Update; - -public class UpdateRequestStatusHandler(RaportsDBContext dbcontext, IHubContext hub) : IRequestHandler -{ - public async Task Handle(UpdateRequestStatusCommand request, CancellationToken cancellationToken) - { - var statusEntity = await dbcontext.RequestStatuses.FirstOrDefaultAsync(x => x.Name == request.UpdateDTO.UpdatedStatusName); - if (statusEntity is null) - { - throw new EntityNotFoundException(nameof(RequestStatus), request.UpdateDTO.UpdatedStatusName); - } - - var requestEntity = await dbcontext.Requests - .Include(x => x.Status) - .Include(x => x.Raport) - .Include(x => x.Period) - .FirstOrDefaultAsync(x => x.ID == request.RequestID); - if (requestEntity is null) - { - throw new EntityNotFoundException(nameof(Request), request.RequestID); - } - - requestEntity.StatusID = statusEntity.ID; - - var entry = dbcontext.Entry(requestEntity); - dbcontext.ChangeTracker.DetectChanges(); - - var wasChanged = entry.Properties.Any(p => p.IsModified) || entry.ComplexProperties.Any(c => c.IsModified); - - var requestDTO = requestEntity.Adapt(); - var response = new ReadRequestResponse(requestDTO); - - if (wasChanged) - { - await dbcontext.SaveChangesAsync(cancellationToken); - await hub.Clients.All.SendAsync("RequestStatusChanged", requestDTO, cancellationToken); - } - - return response; - } -} - -public class UpdateRequestHandler(RaportsDBContext dbcontext, IHubContext hub) : IRequestHandler -{ - public async Task Handle(UpdateRequestCommand request, CancellationToken cancellationToken) - { - Period? periodEntity = null; - - var requestEntity = await dbcontext.Requests - .Include(x => x.Status) - .Include(x => x.Raport) - .Include(x => x.Period) - .FirstOrDefaultAsync(x => x.ID == request.RequestID); - if (requestEntity is null) - { - throw new EntityNotFoundException(nameof(Request), request.RequestID); - } - - if (requestEntity.RaportID == request.UpdateDTO.UpdatedRaportID) - { - throw new Exception(); - } - - // Update raport - if (request.UpdateDTO.UpdatedRaportID is not null) - { - var raportEntity = await dbcontext.Raports.FirstOrDefaultAsync(x => x.ID == (int)request.UpdateDTO.UpdatedRaportID); - if (raportEntity is null) - { - throw new EntityNotFoundException(nameof(Raport), request.UpdateDTO.UpdatedRaportID); - } - - requestEntity.RaportID = raportEntity.ID; - } - - // Update period - if (request.UpdateDTO.UpdatedPeriodID is not null) - { - periodEntity = await dbcontext.Periods.FirstOrDefaultAsync(x => x.ID == (int)request.UpdateDTO.UpdatedPeriodID); - if (periodEntity is null) - { - throw new EntityNotFoundException(nameof(Period), request.UpdateDTO.UpdatedPeriodID); - } - - requestEntity.PeriodID = periodEntity.ID; - } - - if (request.UpdateDTO.UpdatedStartDate is not null && request.UpdateDTO.UpdatedEndDate is not null) - { - DateTime start = (DateTime)request.UpdateDTO.UpdatedStartDate; - DateTime end = (DateTime)request.UpdateDTO.UpdatedEndDate; - - // Remove everyting after hours. - DateTime endDate = new DateTime( - end.Year, - end.Month, - end.Day, - end.Hour, - 0, - 0); - - DateTime startDate = new DateTime( - start.Year, - start.Month, - start.Day, - start.Hour, - 0, - 0); - - requestEntity.StartDate = startDate; - requestEntity.EndDate = endDate; - } - - var entry = dbcontext.Entry(requestEntity); - dbcontext.ChangeTracker.DetectChanges(); - - var wasChanged = entry.Properties.Any(p => p.IsModified) || entry.ComplexProperties.Any(c => c.IsModified); - - var requestDTO = requestEntity.Adapt(); - var response = new ReadRequestResponse(requestDTO); - - if (wasChanged) - { - await dbcontext.SaveChangesAsync(cancellationToken); - await hub.Clients.All.SendAsync("RequestUpdated", requestDTO, cancellationToken); - } - - return response; - } -} - -public class UpdateRequestRaportHandler(RaportsDBContext dbcontext, IHubContext hub) : IRequestHandler -{ - public async Task Handle(UpdateRequestRaportCommand request, CancellationToken cancellationToken) - { - var raportEntity = await dbcontext.Raports.FirstOrDefaultAsync(x => x.ID == request.UpdateDTO.UpdatedRaportID); - if (raportEntity is null) - { - throw new EntityNotFoundException(nameof(Raport), request.UpdateDTO.UpdatedRaportID); - } - - var requestEntity = await dbcontext.Requests - .Include(x => x.Status) - .Include(x => x.Raport) - .Include(x => x.Period) - .FirstOrDefaultAsync(x => x.ID == request.RequestID); - if (requestEntity is null) - { - throw new EntityNotFoundException(nameof(Request), request.RequestID); - } - - requestEntity.RaportID = request.UpdateDTO.UpdatedRaportID; - - var entry = dbcontext.Entry(requestEntity); - dbcontext.ChangeTracker.DetectChanges(); - - var wasChanged = entry.Properties.Any(p => p.IsModified) || entry.ComplexProperties.Any(c => c.IsModified); - - var requestDTO = requestEntity.Adapt(); - var response = new ReadRequestResponse(requestDTO); - - if (wasChanged) - { - await dbcontext.SaveChangesAsync(cancellationToken); - await hub.Clients.All.SendAsync("RequestRaportChanged", requestDTO, cancellationToken); - } - - return response; - } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Application/Mappers/LocationMapper.cs b/Services/Raports/Raports.Application/Mappers/LocationMapper.cs new file mode 100644 index 0000000..6b69a17 --- /dev/null +++ b/Services/Raports/Raports.Application/Mappers/LocationMapper.cs @@ -0,0 +1,13 @@ +namespace Raports.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); + } +} diff --git a/Services/Raports/Raports.Application/Mappers/MeasurementMapper.cs b/Services/Raports/Raports.Application/Mappers/MeasurementMapper.cs new file mode 100644 index 0000000..1a054b3 --- /dev/null +++ b/Services/Raports/Raports.Application/Mappers/MeasurementMapper.cs @@ -0,0 +1,13 @@ +namespace Raports.Application.Mappers; + +internal class MeasurementMapper : 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); + } +} diff --git a/Services/Raports/Raports.Application/Mappers/PeriodMapper.cs b/Services/Raports/Raports.Application/Mappers/PeriodMapper.cs index 836b16e..d7a1f90 100644 --- a/Services/Raports/Raports.Application/Mappers/PeriodMapper.cs +++ b/Services/Raports/Raports.Application/Mappers/PeriodMapper.cs @@ -7,6 +7,8 @@ public void Register(TypeAdapterConfig config) TypeAdapterConfig .NewConfig() .Map(x => x.ID, y => y.ID) + .Map(x => x.MaxAcceptableMissingTimeFrame, y => y.MaxAcceptableMissingTimeFrame) + .Map(x => x.TimeFrame, y => y.TimeFrame) .Map(x => x.Name, y => y.Name); } } diff --git a/Services/Raports/Raports.Application/Mappers/RaportMapper.cs b/Services/Raports/Raports.Application/Mappers/RaportMapper.cs index 9cac981..3720e8d 100644 --- a/Services/Raports/Raports.Application/Mappers/RaportMapper.cs +++ b/Services/Raports/Raports.Application/Mappers/RaportMapper.cs @@ -7,16 +7,6 @@ public void Register(TypeAdapterConfig config) TypeAdapterConfig .NewConfig() .Map(x => x.ID, y => y.ID) - .Map(x => x.CreationDate, y => y.CreationDate) - .Map(x => x.StartDate, y => y.StartDate) - .Map(x => x.EndDate, y => y.EndDate) - .Map(x => x.Period, y => y.Period) - .Map(x => x.Request, y => y.Request); - - TypeAdapterConfig - .NewConfig() - .Map(x => x.ID, y => y.ID) - .Map(x => x.CreationDate, y => y.CreationDate) .Map(x => x.StartDate, y => y.StartDate) .Map(x => x.EndDate, y => y.EndDate) .Map(x => x.Period, y => y.Period); diff --git a/Services/Raports/Raports.Application/Mappers/RequestMapper.cs b/Services/Raports/Raports.Application/Mappers/RequestMapper.cs deleted file mode 100644 index c3dcf6e..0000000 --- a/Services/Raports/Raports.Application/Mappers/RequestMapper.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Raports.Application.Mappers; - -internal class RequestMapper : IRegister -{ - public void Register(TypeAdapterConfig config) - { - TypeAdapterConfig - .NewConfig() - .Map(x => x.ID, y => y.ID) - .Map(x => x.RequestCreationDate, y => y.RequestCreationDate) - .Map(x => x.StartDate, y => y.StartDate) - .Map(x => x.EndDate, y => y.EndDate) - .Map(x => x.Period, y => y.Period) - .Map(x => x.Status, y => y.Status); - - TypeAdapterConfig - .NewConfig() - .Map(x => x.ID, y => y.ID) - .Map(x => x.RequestCreationDate, y => y.RequestCreationDate) - .Map(x => x.StartDate, y => y.StartDate) - .Map(x => x.EndDate, y => y.EndDate) - .Map(x => x.Period, y => y.Period) - .Map(x => x.Status, y => y.Status); - } -} diff --git a/Services/Raports/Raports.Application/Mappers/RequestsStatusMapper.cs b/Services/Raports/Raports.Application/Mappers/StatusMapper.cs similarity index 70% rename from Services/Raports/Raports.Application/Mappers/RequestsStatusMapper.cs rename to Services/Raports/Raports.Application/Mappers/StatusMapper.cs index 8b61e9e..2c4303d 100644 --- a/Services/Raports/Raports.Application/Mappers/RequestsStatusMapper.cs +++ b/Services/Raports/Raports.Application/Mappers/StatusMapper.cs @@ -1,10 +1,10 @@ namespace Raports.Application.Mappers; -internal class RequestsStatusMapper : IRegister +internal class StatusMapper : IRegister { public void Register(TypeAdapterConfig config) { - TypeAdapterConfig + TypeAdapterConfig .NewConfig() .Map(x => x.ID, y => y.ID) .Map(x => x.Name, y => y.Name) diff --git a/Services/Raports/Raports.Application/Messages/AdjustRaport.cs b/Services/Raports/Raports.Application/Messages/AdjustRaport.cs new file mode 100644 index 0000000..a9b3477 --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/AdjustRaport.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class AdjustRaport +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/GenerateDocument.cs b/Services/Raports/Raports.Application/Messages/GenerateDocument.cs new file mode 100644 index 0000000..4bf022f --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/GenerateDocument.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class GenerateDocument +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/GenerateSummary.cs b/Services/Raports/Raports.Application/Messages/GenerateSummary.cs new file mode 100644 index 0000000..cf55510 --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/GenerateSummary.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class GenerateSummary +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/RaportFailed.cs b/Services/Raports/Raports.Application/Messages/RaportFailed.cs new file mode 100644 index 0000000..2a8cde1 --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/RaportFailed.cs @@ -0,0 +1,8 @@ +namespace Raports.Application.Messages; + +internal class RaportFailed +{ + public DateTime FailedDate { get; set; } + public string Description { get; set; } + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/RaportPending.cs b/Services/Raports/Raports.Application/Messages/RaportPending.cs new file mode 100644 index 0000000..ac6c33d --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/RaportPending.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class RaportPending +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/RaportProduceDocument.cs b/Services/Raports/Raports.Application/Messages/RaportProduceDocument.cs new file mode 100644 index 0000000..3f298b2 --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/RaportProduceDocument.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class RaportProduceDocument +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/RaportReady.cs b/Services/Raports/Raports.Application/Messages/RaportReady.cs new file mode 100644 index 0000000..4cee84a --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/RaportReady.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class RaportReady +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/RaportToSummary.cs b/Services/Raports/Raports.Application/Messages/RaportToSummary.cs new file mode 100644 index 0000000..b8890e3 --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/RaportToSummary.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class RaportToSummary +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Messages/ValidateRaport.cs b/Services/Raports/Raports.Application/Messages/ValidateRaport.cs new file mode 100644 index 0000000..bb14465 --- /dev/null +++ b/Services/Raports/Raports.Application/Messages/ValidateRaport.cs @@ -0,0 +1,6 @@ +namespace Raports.Application.Messages; + +internal class ValidateRaport +{ + public DefaultRaportDTO Raport { get; set; } +} diff --git a/Services/Raports/Raports.Application/Raports.Application.csproj b/Services/Raports/Raports.Application/Raports.Application.csproj index 7ff34d1..59f1997 100644 --- a/Services/Raports/Raports.Application/Raports.Application.csproj +++ b/Services/Raports/Raports.Application/Raports.Application.csproj @@ -9,6 +9,8 @@ + + @@ -16,4 +18,12 @@ + + + + + + + + diff --git a/Services/Raports/Raports.DataTransferObjects/DefaultMeasurementDTO.cs b/Services/Raports/Raports.DataTransferObjects/DefaultMeasurementDTO.cs new file mode 100644 index 0000000..ffb93f2 --- /dev/null +++ b/Services/Raports/Raports.DataTransferObjects/DefaultMeasurementDTO.cs @@ -0,0 +1,7 @@ +namespace Raports.DataTransferObjects; + +public record DefaultMeasurementDTO( + int ID, + string Name, + string Unit +); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/RequestStatusDTO.cs b/Services/Raports/Raports.DataTransferObjects/DefaultStatusDTO.cs similarity index 71% rename from Services/Raports/Raports.DataTransferObjects/RequestStatusDTO.cs rename to Services/Raports/Raports.DataTransferObjects/DefaultStatusDTO.cs index 05d3230..eb05e7a 100644 --- a/Services/Raports/Raports.DataTransferObjects/RequestStatusDTO.cs +++ b/Services/Raports/Raports.DataTransferObjects/DefaultStatusDTO.cs @@ -1,6 +1,6 @@ namespace Raports.DataTransferObjects; -public record DefaultRequestStatusDTO( +public record DefaultStatusDTO( int ID, string Name, string Description diff --git a/Services/Raports/Raports.DataTransferObjects/LocationDTO.cs b/Services/Raports/Raports.DataTransferObjects/LocationDTO.cs new file mode 100644 index 0000000..7bb452c --- /dev/null +++ b/Services/Raports/Raports.DataTransferObjects/LocationDTO.cs @@ -0,0 +1,7 @@ +namespace Raports.DataTransferObjects; + +public record DefaultLocationDTO( + int ID, + string Name, + Guid Hash +); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/PeriodDTO.cs b/Services/Raports/Raports.DataTransferObjects/PeriodDTO.cs index ea97599..d0f3ae3 100644 --- a/Services/Raports/Raports.DataTransferObjects/PeriodDTO.cs +++ b/Services/Raports/Raports.DataTransferObjects/PeriodDTO.cs @@ -2,5 +2,7 @@ public record DefaultPeriodDTO( int ID, - string Name + string Name, + TimeSpan TimeFrame, + int MaxAcceptableMissingTimeFrame ); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/RaportDTO.cs b/Services/Raports/Raports.DataTransferObjects/RaportDTO.cs index 9140049..b9eafe2 100644 --- a/Services/Raports/Raports.DataTransferObjects/RaportDTO.cs +++ b/Services/Raports/Raports.DataTransferObjects/RaportDTO.cs @@ -2,17 +2,13 @@ public record DefaultRaportDTO( int ID, - DateTime CreationDate, + DateTime RaportCreationDate, + DateTime RaportCompletedDate, DateTime StartDate, DateTime EndDate, + string Message, DefaultPeriodDTO Period, - RequestDTONoRaport Request + DefaultStatusDTO Status, + ICollection RequestedMeasurements, + ICollection RequestedLocations ); - -public record RaportDTONoRequest( - int ID, - DateTime CreationDate, - DateTime StartDate, - DateTime EndDate, - DefaultPeriodDTO Period -); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/LocationDescription.cs b/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/LocationDescription.cs new file mode 100644 index 0000000..4ff5dd1 --- /dev/null +++ b/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/LocationDescription.cs @@ -0,0 +1,3 @@ +namespace Raports.DataTransferObjects.RaportSummaryContainers; + +internal record LocationDescription(string Name); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/LocationGroupDescription.cs b/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/LocationGroupDescription.cs new file mode 100644 index 0000000..244f5af --- /dev/null +++ b/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/LocationGroupDescription.cs @@ -0,0 +1,3 @@ +namespace Raports.DataTransferObjects.RaportSummaryContainers; + +public record LocationGroupDescription(string Location, string MeasurementType, string Unit, string Period, string Samples); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/SampleDescription.cs b/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/SampleDescription.cs new file mode 100644 index 0000000..b3502f6 --- /dev/null +++ b/Services/Raports/Raports.DataTransferObjects/RaportSummaryContainers/SampleDescription.cs @@ -0,0 +1,3 @@ +namespace Raports.DataTransferObjects.RaportSummaryContainers; + +public record SampleDescription(DateTime Date, double Value); \ No newline at end of file diff --git a/Services/Raports/Raports.DataTransferObjects/RequestDTO.cs b/Services/Raports/Raports.DataTransferObjects/RequestDTO.cs deleted file mode 100644 index 8137dd1..0000000 --- a/Services/Raports/Raports.DataTransferObjects/RequestDTO.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace Raports.DataTransferObjects; - -public record CreateRequestDTO( - DateTime StartDate, - DateTime EndDate, - string PeriodName -); - -public record DefaultRequestDTO( - int ID, - DateTime RequestCreationDate, - DateTime StartDate, - DateTime EndDate, - DefaultRequestStatusDTO Status, - RaportDTONoRequest? Raport, - DefaultPeriodDTO Period -); - -public record RequestDTONoRaport( - int ID, - DateTime RequestCreationDate, - DateTime StartDate, - DateTime EndDate, - DefaultRequestStatusDTO Status, - DefaultPeriodDTO Period -); - -public record UpdateRequestStatusDTO( - string UpdatedStatusName -); - -public record UpdateRequestDTO( - DateTime? UpdatedStartDate, - DateTime? UpdatedEndDate, - int? UpdatedPeriodID, - int? UpdatedRaportID -); - -public record UpdateRequestRaportDTO( - int UpdatedRaportID -); \ No newline at end of file diff --git a/Services/Raports/Raports.Domain/Entities/Location.cs b/Services/Raports/Raports.Domain/Entities/Location.cs new file mode 100644 index 0000000..0f8c63a --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/Location.cs @@ -0,0 +1,8 @@ +namespace Raports.Domain.Entities; + +public class Location +{ + public int ID { get; set; } + public string Name { get; set; } + public Guid Hash { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/LocationGroup.cs b/Services/Raports/Raports.Domain/Entities/LocationGroup.cs new file mode 100644 index 0000000..c67d7cd --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/LocationGroup.cs @@ -0,0 +1,15 @@ +namespace Raports.Domain.Entities; + +public class LocationGroup +{ + public int ID { get; set; } + public string Summary { get; set; } + + public int LocationID { get; set; } + public Location Location { get; set; } + + public int MeasurementGroupID { get; set; } + public MeasurementGroup MeasurementGroup { get; set; } + + public ICollection SampleGroups { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/Measurement.cs b/Services/Raports/Raports.Domain/Entities/Measurement.cs new file mode 100644 index 0000000..dabfc81 --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/Measurement.cs @@ -0,0 +1,10 @@ +namespace Raports.Domain.Entities; + +public class Measurement +{ + public int ID { get; set; } + public string Name { get; set; } + public string Unit { get; set; } + public int MinChartYValue { get; set; } + public int MaxChartYValue { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/MeasurementAnalysisModel.cs b/Services/Raports/Raports.Domain/Entities/MeasurementAnalysisModel.cs deleted file mode 100644 index c09c24d..0000000 --- a/Services/Raports/Raports.Domain/Entities/MeasurementAnalysisModel.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Text.Json; - -namespace Raports.Domain.Entities; - -public class MeasurementAnalysisModel -{ - public string MeasurementType { get; set; } - public DateTime[] Time { get; set; } - public List Measurements { get; set; } - - public string ToJson() - { - string result = JsonSerializer.Serialize(this); - - return result; - } -} - - -public static class MeasurementAnalysisModelExtensions -{ - public static MeasurementAnalysisModel ToAnalysisModel(this MeasurementPacket entity) - { - return new MeasurementAnalysisModel() - { - Time = entity.Time, - MeasurementType = entity.MeasurementName, - Measurements = entity.Measurements - }; - } -} diff --git a/Services/Raports/Raports.Domain/Entities/MeasurementData.cs b/Services/Raports/Raports.Domain/Entities/MeasurementData.cs deleted file mode 100644 index 38e5433..0000000 --- a/Services/Raports/Raports.Domain/Entities/MeasurementData.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Raports.Domain.Entities; - -public class MeasurementData -{ - /// - /// Data label. For for example: 'Living room', 'Kitchen' or 'Device 1'. - /// - public string Name { get; set; } - - /// - /// Raw data - /// - public double[] Data { get; set; } - public MeasurementData(string name, double[] data) - { - Name = name; - Data = data; - } -} diff --git a/Services/Raports/Raports.Domain/Entities/MeasurementGroup.cs b/Services/Raports/Raports.Domain/Entities/MeasurementGroup.cs new file mode 100644 index 0000000..c38455c --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/MeasurementGroup.cs @@ -0,0 +1,15 @@ +namespace Raports.Domain.Entities; + +public class MeasurementGroup +{ + public int ID { get; set; } + public string Summary { get; set; } + + public int RaportID { get; set; } + public Raport Raport { get; set; } + + public int MeasurementID { get; set; } + public Measurement Measurement { get; set; } + + public ICollection LocationGroups { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/MeasurementPacket.cs b/Services/Raports/Raports.Domain/Entities/MeasurementPacket.cs deleted file mode 100644 index adc6a23..0000000 --- a/Services/Raports/Raports.Domain/Entities/MeasurementPacket.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Raports.Domain.Entities -{ - public class MeasurementPacket - { - public string MeasurementName { get; set; } - public string Description { get; set; } - public DateTime[] Time { get; set; } - public List Measurements { get; set; } - public MeasurementPacket(DateTime[] time, List measurements, int dataDisplayModulo, - int maxY, int minY, string name, string description) - { - Time = time; - Measurements = measurements; - MeasurementName = name; - Description = description; - } - } -} \ No newline at end of file diff --git a/Services/Raports/Raports.Domain/Entities/Period.cs b/Services/Raports/Raports.Domain/Entities/Period.cs index 2d2d50f..82fc023 100644 --- a/Services/Raports/Raports.Domain/Entities/Period.cs +++ b/Services/Raports/Raports.Domain/Entities/Period.cs @@ -4,4 +4,6 @@ public class Period { public int ID { get; set; } public string Name { get; set; } + public TimeSpan TimeFrame { get; set; } + public int MaxAcceptableMissingTimeFrame { get; set; } } \ No newline at end of file diff --git a/Services/Raports/Raports.Domain/Entities/Raport.cs b/Services/Raports/Raports.Domain/Entities/Raport.cs index aadb38c..98be9b6 100644 --- a/Services/Raports/Raports.Domain/Entities/Raport.cs +++ b/Services/Raports/Raports.Domain/Entities/Raport.cs @@ -3,11 +3,21 @@ public class Raport { public int ID { get; set; } - public DateTime CreationDate { get; set; } + public DateTime RaportCreationDate { get; set; } + public DateTime RaportCompletedDate { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } + public string Message { get; set; } + + public Guid DocumentHash { get; set; } + public int PeriodID { get; set; } public Period Period { get; set; } - public int RequestID { get; set; } - public Request Request { get; set; } + + public int StatusID { get; set; } + public Status Status { get; set; } + + public ICollection MeasurementGroups { get; set; } + public ICollection RequestedMeasurements { get; set; } + public ICollection RequestedLocations { get; set; } } diff --git a/Services/Raports/Raports.Domain/Entities/Request.cs b/Services/Raports/Raports.Domain/Entities/Request.cs deleted file mode 100644 index a200fa7..0000000 --- a/Services/Raports/Raports.Domain/Entities/Request.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Raports.Domain.Entities; - -public class Request -{ - public int ID { get; set; } - public DateTime RequestCreationDate { get; set; } - public DateTime StartDate { get; set; } - public DateTime EndDate { get; set; } - public int StatusID { get; set; } - public RequestStatus Status { get; set; } - public int RaportID { get; set; } - public Raport Raport { get; set; } - public int PeriodID { get; set; } - public Period Period { get; set; } -} diff --git a/Services/Raports/Raports.Domain/Entities/RequestedLocation.cs b/Services/Raports/Raports.Domain/Entities/RequestedLocation.cs new file mode 100644 index 0000000..8d57b9f --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/RequestedLocation.cs @@ -0,0 +1,11 @@ +namespace Raports.Domain.Entities; + +public class RequestedLocation +{ + public int ID { get; set; } + + public int LocationID { get; set; } + public Location Location { get; set; } + public int RaportID { get; set; } + public Raport Raport { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/RequestedMeasurement.cs b/Services/Raports/Raports.Domain/Entities/RequestedMeasurement.cs new file mode 100644 index 0000000..91f1403 --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/RequestedMeasurement.cs @@ -0,0 +1,11 @@ +namespace Raports.Domain.Entities; + +public class RequestedMeasurement +{ + public int ID { get; set; } + + public int MeasurementID { get; set; } + public Measurement Measurement { get; set; } + public int RaportID { get; set; } + public Raport Raport { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/SampleGroup.cs b/Services/Raports/Raports.Domain/Entities/SampleGroup.cs new file mode 100644 index 0000000..a8cda1f --- /dev/null +++ b/Services/Raports/Raports.Domain/Entities/SampleGroup.cs @@ -0,0 +1,11 @@ +namespace Raports.Domain.Entities; + +public class SampleGroup +{ + public int ID { get; set; } + public DateTime Date { get; set; } + public double Value { get; set; } + + public int LocationGroupID { get; set; } + public LocationGroup LocationGroup { get; set; } +} diff --git a/Services/Raports/Raports.Domain/Entities/RequestStatus.cs b/Services/Raports/Raports.Domain/Entities/Status.cs similarity index 85% rename from Services/Raports/Raports.Domain/Entities/RequestStatus.cs rename to Services/Raports/Raports.Domain/Entities/Status.cs index c74acca..f993d54 100644 --- a/Services/Raports/Raports.Domain/Entities/RequestStatus.cs +++ b/Services/Raports/Raports.Domain/Entities/Status.cs @@ -1,6 +1,6 @@ namespace Raports.Domain.Entities; -public class RequestStatus +public class Status { public int ID { get; set; } public string Name { get; set; } diff --git a/Services/Raports/Raports.Domain/Raports.Domain.csproj b/Services/Raports/Raports.Domain/Raports.Domain.csproj index ee8fa66..a0169b1 100644 --- a/Services/Raports/Raports.Domain/Raports.Domain.csproj +++ b/Services/Raports/Raports.Domain/Raports.Domain.csproj @@ -7,7 +7,9 @@ - + + + diff --git a/Services/Raports/Raports.Infrastructure/Configuration/LocationEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/LocationEntityConfiguration.cs new file mode 100644 index 0000000..0ab7121 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/LocationEntityConfiguration.cs @@ -0,0 +1,12 @@ +namespace Raports.Infrastructure.Configuration; + +public class LocationEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + builder.Property(x => x.Name).HasComment("For example: 'Kitchen', 'Attic' etc..."); + builder.Property(x => x.Hash).IsRequired(); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/LocationGroupEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/LocationGroupEntityConfiguration.cs new file mode 100644 index 0000000..cab55f8 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/LocationGroupEntityConfiguration.cs @@ -0,0 +1,18 @@ +namespace Raports.Infrastructure.Configuration; + +internal class LocationGroupEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + + builder.Property(x => x.Summary).HasComment("Verbal summary of data stored for this location"); + + builder.HasOne(x => x.MeasurementGroup) + .WithMany(x => x.LocationGroups) + .HasForeignKey(x => x.MeasurementGroupID) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/MeasruementEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/MeasruementEntityConfiguration.cs new file mode 100644 index 0000000..d57a3d4 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/MeasruementEntityConfiguration.cs @@ -0,0 +1,11 @@ +namespace Raports.Infrastructure.Configuration; + +internal class MeasruementEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + builder.Property(x => x.Name); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/MeasurementGroupEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/MeasurementGroupEntityConfiguration.cs new file mode 100644 index 0000000..d89ed33 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/MeasurementGroupEntityConfiguration.cs @@ -0,0 +1,18 @@ +namespace Raports.Infrastructure.Configuration; + +internal class MeasurementGroupEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + + builder.Property(x => x.Summary).HasComment("Combined summary for all location groups"); + + builder.HasOne(x => x.Raport) + .WithMany(x => x.MeasurementGroups) + .HasForeignKey(x => x.RaportID) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/PeriodEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/PeriodEntityConfiguration.cs index 5b05968..5a56607 100644 --- a/Services/Raports/Raports.Infrastructure/Configuration/PeriodEntityConfiguration.cs +++ b/Services/Raports/Raports.Infrastructure/Configuration/PeriodEntityConfiguration.cs @@ -6,7 +6,13 @@ public void Configure(EntityTypeBuilder builder) { builder.HasKey(x => x.ID); builder.Property(b => b.ID).ValueGeneratedOnAdd(); - + builder.Property(x => x.MaxAcceptableMissingTimeFrame).HasDefaultValue(1); + builder.Property(x => x.TimeFrame).HasDefaultValue(TimeSpan.Zero); builder.Property(x => x.Name).HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + builder.ToTable(x => + { + x.HasCheckConstraint("CK_Period_MaxAcceptableMissingTimeFrame_Range", "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + }); } } diff --git a/Services/Raports/Raports.Infrastructure/Configuration/RaportEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/RaportEntityConfiguration.cs index ca6ee47..141748e 100644 --- a/Services/Raports/Raports.Infrastructure/Configuration/RaportEntityConfiguration.cs +++ b/Services/Raports/Raports.Infrastructure/Configuration/RaportEntityConfiguration.cs @@ -1,23 +1,29 @@ -namespace Raports.Infrastructure.Configuration +namespace Raports.Infrastructure.Configuration; + +internal class RaportEntityConfiguration : IEntityTypeConfiguration { - internal class RaportEntityConfiguration : IEntityTypeConfiguration + public void Configure(EntityTypeBuilder builder) { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(x => x.ID); - builder.Property(b => b.ID).ValueGeneratedOnAdd(); + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + + builder.Property(x => x.RaportCreationDate).HasComment("Date of Raport creation"); + builder.Property(x => x.RaportCompletedDate).HasComment("Date of Raport completion"); + builder.Property(x => x.StartDate).HasComment("Date of first measurement"); + builder.Property(x => x.EndDate).HasComment("Date of last measurement"); + builder.Property(x => x.DocumentHash).HasDefaultValue(Guid.Empty).HasComment("Hash that allows to identify PDF in Azure Blob Storage"); - builder.Property(x => x.CreationDate).HasComment("Date when raport was created"); + builder.HasOne(x => x.Period) + .WithMany() + .HasForeignKey(x => x.PeriodID) + .IsRequired(); - builder.HasOne(x => x.Request) - .WithOne(r => r.Raport) - .HasForeignKey(x => x.RequestID) - .IsRequired(false); + builder.HasMany(x => x.RequestedLocations) + .WithMany() + .UsingEntity(); - builder.HasOne(x => x.Period) - .WithMany() - .HasForeignKey(x => x.PeriodID) - .IsRequired(); - } + builder.HasMany(x => x.RequestedMeasurements) + .WithMany() + .UsingEntity(); } } diff --git a/Services/Raports/Raports.Infrastructure/Configuration/RequestEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/RequestEntityConfiguration.cs deleted file mode 100644 index 473796b..0000000 --- a/Services/Raports/Raports.Infrastructure/Configuration/RequestEntityConfiguration.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Raports.Infrastructure.Configuration; - -internal class RequestEntityConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(x => x.ID); - builder.Property(b => b.ID).ValueGeneratedOnAdd(); - - builder.Property(x => x.StartDate).HasComment("Raport will be generate from this date"); - builder.Property(x => x.EndDate).HasComment("Raport will be generate to this date"); - builder.Property(x => x.RequestCreationDate).HasComment("Date when request was created"); - - builder.HasOne(x => x.Status) - .WithMany() - .HasForeignKey(x => x.StatusID) - .IsRequired(); - - builder.HasOne(x => x.Period) - .WithMany() - .HasForeignKey(x => x.PeriodID) - .IsRequired(); - } -} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/RequestStatusEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/RequestStatusEntityConfiguration.cs deleted file mode 100644 index c799f78..0000000 --- a/Services/Raports/Raports.Infrastructure/Configuration/RequestStatusEntityConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Raports.Infrastructure.Configuration -{ - internal class RequestStatusEntityConfiguration : IEntityTypeConfiguration - { - public void Configure(EntityTypeBuilder builder) - { - builder.HasKey(x => x.ID); - builder.Property(b => b.ID).ValueGeneratedOnAdd(); - - builder.Property(x => x.Name).HasComment("For example: 'Generated', 'Pending', etc..."); - builder.Property(x => x.Description).HasComment("What is exactly happening here"); - } - } -} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/RequestedLocationEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/RequestedLocationEntityConfiguration.cs new file mode 100644 index 0000000..9cc142a --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/RequestedLocationEntityConfiguration.cs @@ -0,0 +1,10 @@ +namespace Raports.Infrastructure.Configuration; + +internal class RequestedLocationEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/RequestedMeasurementEntityConfig.cs b/Services/Raports/Raports.Infrastructure/Configuration/RequestedMeasurementEntityConfig.cs new file mode 100644 index 0000000..ccccf6d --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/RequestedMeasurementEntityConfig.cs @@ -0,0 +1,10 @@ +namespace Raports.Infrastructure.Configuration; + +internal class RequestedMeasurementEntityConfig : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Configuration/SampleGroupEntityConfiguration.cs b/Services/Raports/Raports.Infrastructure/Configuration/SampleGroupEntityConfiguration.cs new file mode 100644 index 0000000..6452ed9 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Configuration/SampleGroupEntityConfiguration.cs @@ -0,0 +1,18 @@ +namespace Raports.Infrastructure.Configuration; + +internal class SampleGroupEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.ID); + builder.Property(b => b.ID).ValueGeneratedOnAdd(); + builder.Property(x => x.Date).HasComment("Time of measurement"); + builder.Property(x => x.Value).HasComment("Value of measurement"); + + builder.HasOne(x => x.LocationGroup) + .WithMany(x => x.SampleGroups) + .HasForeignKey(x => x.LocationGroupID) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); + } +} diff --git a/Services/Raports/Raports.Infrastructure/Database/RaportsDBContext.cs b/Services/Raports/Raports.Infrastructure/Database/RaportsDBContext.cs index 8b398b4..b74fe13 100644 --- a/Services/Raports/Raports.Infrastructure/Database/RaportsDBContext.cs +++ b/Services/Raports/Raports.Infrastructure/Database/RaportsDBContext.cs @@ -4,10 +4,16 @@ public class RaportsDBContext : DbContext { public RaportsDBContext(DbContextOptions options) : base(options) { } - public DbSet Requests { get; set; } + public DbSet Locations { get; set; } + public DbSet Measurements { get; set; } + public DbSet MeasurementGroups { get; set; } + public DbSet LocationGroups { get; set; } + public DbSet SampleGroups { get; set; } public DbSet Periods { get; set; } - public DbSet RequestStatuses { get; set; } + public DbSet Statuses { get; set; } public DbSet Raports { get; set; } + public DbSet RequestedLocations { get; set; } + public DbSet RequestedMeasurements { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/Services/Raports/Raports.Infrastructure/DependencyInjection.cs b/Services/Raports/Raports.Infrastructure/DependencyInjection.cs index 1b3fc9e..3466ac3 100644 --- a/Services/Raports/Raports.Infrastructure/DependencyInjection.cs +++ b/Services/Raports/Raports.Infrastructure/DependencyInjection.cs @@ -1,36 +1,57 @@ -namespace Raports.Infrastructure +using OpenAI.Chat; + +namespace Raports.Infrastructure; + +public static class DependencyInjection { - public static class DependencyInjection + public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration config, IWebHostEnvironment environment) { - public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration config, IWebHostEnvironment environment) + // Configure Azure Blob Storage + string azureBloblStorageConnectionString = config.GetConnectionString("AzureBlobStorage"); + + string azureBlobStorageContainerName = environment.IsProduction() + ? config["AzureBlob:ContainerName_Prod"] + : config["AzureBlob:ContainerName_Dev"]; + + var blobServiceClient = new BlobServiceClient(azureBloblStorageConnectionString); + var blobContainerClient = blobServiceClient.GetBlobContainerClient(azureBlobStorageContainerName); + services.AddSingleton(blobServiceClient); + services.AddSingleton(blobContainerClient); + + // Configure OpenAI + var model = config["OpenAI:Model"]; + var openAIApiKey = config["OpenAI:API_KEY"]; + var openAIClient = new ChatClient(model, openAIApiKey); + + services.AddSingleton(openAIClient); + + // Configure Database + services.AddDbContext(options => { - string connectionString = string.Empty; + string databaseConnectionString = string.Empty; if (environment.IsDevelopment()) { - connectionString = config.GetConnectionString("RaportsDB_Dev"); + databaseConnectionString = config.GetConnectionString("RaportsDB_Dev"); } else if (environment.IsStaging()) { - connectionString = config.GetConnectionString("RaportsDB_Dev"); + databaseConnectionString = config.GetConnectionString("RaportsDB_Dev"); } else { - connectionString = config.GetConnectionString("RaportsDB_Prod"); + databaseConnectionString = config.GetConnectionString("RaportsDB_Prod"); } - services.AddDbContext(options => + options.UseSqlServer(databaseConnectionString, sqlOptions => { - options.UseSqlServer(connectionString, sqlOptions => - { - sqlOptions.EnableRetryOnFailure( - maxRetryCount: 5, - maxRetryDelay: TimeSpan.FromSeconds(10), - errorNumbersToAdd: null - ); - }); + sqlOptions.EnableRetryOnFailure( + maxRetryCount: 5, + maxRetryDelay: TimeSpan.FromSeconds(10), + errorNumbersToAdd: null + ); }); + }); - return services; - } + return services; } } diff --git a/Services/Raports/Raports.Infrastructure/Extensions/RaportsDatabaseExtensions.cs b/Services/Raports/Raports.Infrastructure/Extensions/RaportsDatabaseExtensions.cs index 4ac61b5..4aa8634 100644 --- a/Services/Raports/Raports.Infrastructure/Extensions/RaportsDatabaseExtensions.cs +++ b/Services/Raports/Raports.Infrastructure/Extensions/RaportsDatabaseExtensions.cs @@ -1,111 +1,111 @@ -namespace Raports.Infrastructure.Extensions; +//namespace Raports.Infrastructure.Extensions; -public static class RaportsDatabaseExtensions -{ - public static async Task InitializeDatabaseAsync(this WebApplication app) - { - using var scope = app.Services.CreateScope(); +//public static class RaportsDatabaseExtensions +//{ +// public static async Task InitializeDatabaseAsync(this WebApplication app) +// { +// using var scope = app.Services.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); +// var context = scope.ServiceProvider.GetRequiredService(); - context.Database.MigrateAsync().GetAwaiter().GetResult(); +// context.Database.MigrateAsync().GetAwaiter().GetResult(); - await SeedAsync(context); - } +// await SeedAsync(context); +// } - private static async Task SeedAsync(RaportsDBContext context) - { - var tables = new string[] - { - nameof(context.Raports), - nameof(context.Requests), - nameof(context.Periods), - nameof(context.RequestStatuses), - }; +// private static async Task SeedAsync(RaportsDBContext context) +// { +// var tables = new string[] +// { +// nameof(context.Raports), +// nameof(context.Requests), +// nameof(context.Periods), +// nameof(context.RequestStatuses), +// }; - foreach (var table in tables) - { - await context.Database.ExecuteSqlRawAsync($"DELETE FROM [{table}]"); - await context.Database.ExecuteSqlRawAsync($"DBCC CHECKIDENT ('[{table}]', RESEED, 0)"); - } +// foreach (var table in tables) +// { +// await context.Database.ExecuteSqlRawAsync($"DELETE FROM [{table}]"); +// await context.Database.ExecuteSqlRawAsync($"DBCC CHECKIDENT ('[{table}]', RESEED, 0)"); +// } - await SeedPeriodsAsync(context); - await SeedStatusedAsync(context); - await SeedRequestsAsync(context); - await SeedRaportsAsync(context); - } +// await SeedPeriodsAsync(context); +// await SeedStatusedAsync(context); +// await SeedRequestsAsync(context); +// await SeedRaportsAsync(context); +// } - private static async Task SeedPeriodsAsync(RaportsDBContext context) - { - if (!await context.Periods.AnyAsync()) - { - IEnumerable periodsTestSet = new List() - { - new Period(){ Name = "Hourly" }, - new Period(){ Name = "Daily" }, - new Period(){ Name = "Weekly" }, - new Period(){ Name = "Monthly" }, - }; +// private static async Task SeedPeriodsAsync(RaportsDBContext context) +// { +// if (!await context.Periods.AnyAsync()) +// { +// IEnumerable periodsTestSet = new List() +// { +// new Period(){ Name = "Hourly" }, +// new Period(){ Name = "Daily" }, +// new Period(){ Name = "Weekly" }, +// new Period(){ Name = "Monthly" }, +// }; - await context.Periods.AddRangeAsync(periodsTestSet); - await context.SaveChangesAsync(); - } - } +// await context.Periods.AddRangeAsync(periodsTestSet); +// await context.SaveChangesAsync(); +// } +// } - private static async Task SeedStatusedAsync(RaportsDBContext context) - { - if (!await context.RequestStatuses.AnyAsync()) - { - IEnumerable statusesTestSet = new List() - { - new RequestStatus(){ Name = "Suspended", Description = "Wait for user interaction" }, - new RequestStatus(){ Name = "Pending", Description = "This request is waiting for it's computation" }, - new RequestStatus(){ Name = "Processing", Description = "This request is being computated" }, - new RequestStatus(){ Name = "Completed", Description = "Raport was created successfully" }, - new RequestStatus(){ Name = "Failed", Description = "Data for this report is not sufficient" }, - new RequestStatus(){ Name = "Deleted", Description = "This request was deleted" }, - }; +// private static async Task SeedStatusedAsync(RaportsDBContext context) +// { +// if (!await context.RequestStatuses.AnyAsync()) +// { +// IEnumerable statusesTestSet = new List() +// { +// new Status(){ Name = "Suspended", Description = "Wait for user interaction" }, +// new Status(){ Name = "Pending", Description = "This request is waiting for it's computation" }, +// new Status(){ Name = "Processing", Description = "This request is being computated" }, +// new Status(){ Name = "Completed", Description = "Raport was created successfully" }, +// new Status(){ Name = "Failed", Description = "Data for this report is not sufficient" }, +// new Status(){ Name = "Deleted", Description = "This request was deleted" }, +// }; - await context.RequestStatuses.AddRangeAsync(statusesTestSet); - await context.SaveChangesAsync(); - } - } +// await context.RequestStatuses.AddRangeAsync(statusesTestSet); +// await context.SaveChangesAsync(); +// } +// } - private static async Task SeedRequestsAsync(RaportsDBContext context) - { - if (!await context.Requests.AnyAsync()) - { - IEnumerable requestTestSet = new List() - { - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-1), EndDate = DateTime.Now.AddDays(1), StatusID = 1, PeriodID = 1 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-2), EndDate = DateTime.Now.AddDays(2), StatusID = 2, PeriodID = 2 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-3), EndDate = DateTime.Now.AddDays(3), StatusID = 3, PeriodID = 1 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-4), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 2 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-5), EndDate = DateTime.Now.AddDays(3), StatusID = 1, PeriodID = 1 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-6), EndDate = DateTime.Now.AddDays(3), StatusID = 3, PeriodID = 2 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-7), EndDate = DateTime.Now.AddDays(3), StatusID = 1, PeriodID = 1 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-8), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 2 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-9), EndDate = DateTime.Now.AddDays(3), StatusID = 3, PeriodID = 1 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-10), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 2 }, - new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-11), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 1 }, - }; +// private static async Task SeedRequestsAsync(RaportsDBContext context) +// { +// if (!await context.Requests.AnyAsync()) +// { +// IEnumerable requestTestSet = new List() +// { +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-1), EndDate = DateTime.Now.AddDays(1), StatusID = 1, PeriodID = 1 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-2), EndDate = DateTime.Now.AddDays(2), StatusID = 2, PeriodID = 2 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-3), EndDate = DateTime.Now.AddDays(3), StatusID = 3, PeriodID = 1 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-4), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 2 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-5), EndDate = DateTime.Now.AddDays(3), StatusID = 1, PeriodID = 1 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-6), EndDate = DateTime.Now.AddDays(3), StatusID = 3, PeriodID = 2 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-7), EndDate = DateTime.Now.AddDays(3), StatusID = 1, PeriodID = 1 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-8), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 2 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-9), EndDate = DateTime.Now.AddDays(3), StatusID = 3, PeriodID = 1 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-10), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 2 }, +// new Request(){ RequestCreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-11), EndDate = DateTime.Now.AddDays(3), StatusID = 2, PeriodID = 1 }, +// }; - await context.Requests.AddRangeAsync(requestTestSet); - await context.SaveChangesAsync(); - } - } +// await context.Requests.AddRangeAsync(requestTestSet); +// await context.SaveChangesAsync(); +// } +// } - private static async Task SeedRaportsAsync(RaportsDBContext context) - { - if (!await context.Raports.AnyAsync()) - { - IEnumerable raportsTestSet = new List() - { - //new Raport(){ CreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-1), EndDate = DateTime.Now.AddDays(1), PeriodID = 1, RequestID = 1 }, - }; +// private static async Task SeedRaportsAsync(RaportsDBContext context) +// { +// if (!await context.Raports.AnyAsync()) +// { +// IEnumerable raportsTestSet = new List() +// { +// //new Raport(){ CreationDate = DateTime.Now, StartDate = DateTime.Now.AddDays(-1), EndDate = DateTime.Now.AddDays(1), PeriodID = 1, RequestID = 1 }, +// }; - await context.Raports.AddRangeAsync(raportsTestSet); - await context.SaveChangesAsync(); - } - } -} +// await context.Raports.AddRangeAsync(raportsTestSet); +// await context.SaveChangesAsync(); +// } +// } +//} diff --git a/Services/Raports/Raports.Infrastructure/Generators/MeasurementPacketGenerator.cs b/Services/Raports/Raports.Infrastructure/Generators/MeasurementPacketGenerator.cs deleted file mode 100644 index 14647a4..0000000 --- a/Services/Raports/Raports.Infrastructure/Generators/MeasurementPacketGenerator.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Raports.Infrastructure.Generators; - -public class MeasurementPacketGenerator -{ - //public readonly MeasurementsClientGRPC _measurementsGrpcClient; - //public readonly DevicesClientGRPC _devicesGrpcClient; - //private readonly AiResponseClientGRPC _aiGrpcClient; - public readonly IConfiguration _configuration; - public MeasurementPacketGenerator(IConfiguration configuration) - { - _configuration = configuration; - //_measurementsGrpcClient = new MeasurementsClientGRPC(configuration); - //_devicesGrpcClient = new DevicesClientGRPC(configuration); - //_aiGrpcClient = new AiResponseClientGRPC(configuration); - } - - public async Task> ProcessDailyDataForReport(DateTime date) - { - //IEnumerable measurements = await _measurementsGrpcClient.GetAllMeasurementsFromDay(date); - //IEnumerable devices = await _devicesGrpcClient.GetAllDevices(); - - // This steps allow to convert data from `Measurements` and `Devices` collection into usable packets of - // usable data. For example, `Temperatures` package that contains datasets for each of available locations. - //List measurementsPackets = MeasurementPacketFiller.AgregateDailyData(null, null); - - //var tasks = new List(); - - //foreach (MeasurementPacket packet in measurementsPackets) - //{ - // MeasurementAnalysisModel model = packet.ToAnalysisModel(); - // string promptData = model.ToJson(); - - // //AiResponseGRPC response = await _aiGrpcClient.GenerateDescriptionForDailyRaport(promptData); - - // packet.Description = null; - //} - - - return null; - //return measurementsPackets; - } -} diff --git a/Services/Raports/Raports.Infrastructure/Generators/RaportGenerator.cs b/Services/Raports/Raports.Infrastructure/Generators/RaportGenerator.cs index 793ff3e..7c52ef9 100644 --- a/Services/Raports/Raports.Infrastructure/Generators/RaportGenerator.cs +++ b/Services/Raports/Raports.Infrastructure/Generators/RaportGenerator.cs @@ -1,137 +1,137 @@ -using QuestPDF.Helpers; -using QuestPDF.Infrastructure; -using ScottPlot; - -namespace Raports.Infrastructure.Generators; - -public static class RaportGenerator -{ - public static byte[] Logo = Properties.Resources.HomeeLogo; - public static Document GenerateRaport(IEnumerable measurementsPackets, DateTime date) - { - QuestPDF.Settings.License = LicenseType.Community; - - var document = Document.Create(container => - { - container.Page(page => - { - page.Size(PageSizes.A4); - page.Margin(15, Unit.Point); - - page.Footer().AlignCenter() - .Text(text => - { - text.CurrentPageNumber(); - text.Span(" / "); - text.TotalPages(); - }); - - page.Header().Element(x => ComposeHeader(x, date)); - page.Content().Element(x => ComposeContent(x, measurementsPackets)); - }); - }); - - var currTitle = document.GetMetadata().Title; - - string fileName = $"Raport-{date:yyyy-MM-dd}-Timestamp-{DateTime.Now:yyyy-MM-dd_HH-mm-ss}"; - - document.GetMetadata().Title = fileName; - - //document.ShowInCompanion(); - - return document; - } - - private static void ComposeHeader(IContainer container, DateTime date) - { - container.Row(row => - { - row.RelativeItem().Column(column => - { - column.Item().Width(64).Height(64).AlignLeft().Image(Logo); - }); - - row.RelativeItem().Column(column => - { - column.Item().Text($"Daily raport").AlignCenter().Bold().FontSize(20); - }); - - row.RelativeItem().Column(column => - { - column.Item().Text($"Date: {date.ToString("dd.MM.yyyy")}").AlignRight(); - }); - }); - } - - private static void ComposeChart(IContainer container, MeasurementPacket packet) - { - container.Svg(size => - { - ScottPlot.Plot myPlot = new(); - - foreach (var data in packet.Measurements) - { - var scatter = myPlot.Add.Scatter(packet.Time, data.Data); - scatter.LegendText = data.Name; - } - - myPlot.YLabel(packet.MeasurementName, 12); - myPlot.XLabel("Time", 12); - - //myPlot.Axes.SetLimitsY(minY, maxY); - //myPlot.Axes.SetLimitsY(packet.MinY, packet.MaxY); - - myPlot.Axes.Bottom.TickLabelStyle.Rotation = -45; - myPlot.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleRight; - - // create a manual DateTime tick generator and add ticks - ScottPlot.TickGenerators.DateTimeManual ticks = new(); - - // add ticks for Mondays only - for (int i = 0; i < packet.Time.Length; i++) - { - if (i == (packet.Time.Length - 1) || i % 4 == 0) - { - DateTime date = packet.Time[i]; - string label = $"{date:HH}:{date:ss}"; - ticks.AddMajor(date, label); - } - } - - // tell the horizontal axis to use the custom tick generator - myPlot.Axes.Bottom.TickGenerator = ticks; - - myPlot.ShowLegend(); - - //myPlot.SavePng("demo.png", 100, 100); - - return myPlot.GetSvgXml((int)size.Width, (int)size.Height); - }); - } - - private static void ComposeContent(IContainer container, IEnumerable packets) - { - container.MultiColumn(multiColumn => - { - multiColumn.Columns(2); - multiColumn.Spacing(15); - - multiColumn.Content() - .Column(column => - { - column.Spacing(15); - - foreach (var packet in packets) - { - column.Item().Text(packet.MeasurementName).AlignCenter().Bold().FontSize(16); - - column.Item().Text(packet.Description).Justify(); - - column.Item().AspectRatio(1).Element(x => ComposeChart(x, packet)); - - column.Item().LineHorizontal(1); - } - }); - }); - } -} +//using QuestPDF.Helpers; +//using QuestPDF.Infrastructure; +//using ScottPlot; + +//namespace Raports.Infrastructure.Generators; + +//public static class RaportGenerator +//{ +// public static byte[] Logo = Properties.Resources.HomeeLogo; +// public static Document GenerateRaport(IEnumerable measurementsPackets, DateTime date) +// { +// QuestPDF.Settings.License = LicenseType.Community; + +// var document = Document.Create(container => +// { +// container.Page(page => +// { +// page.Size(PageSizes.A4); +// page.Margin(15, Unit.Point); + +// page.Footer().AlignCenter() +// .Text(text => +// { +// text.CurrentPageNumber(); +// text.Span(" / "); +// text.TotalPages(); +// }); + +// page.Header().Element(x => ComposeHeader(x, date)); +// page.Content().Element(x => ComposeContent(x, measurementsPackets)); +// }); +// }); + +// var currTitle = document.GetMetadata().Title; + +// string fileName = $"Raport-{date:yyyy-MM-dd}-Timestamp-{DateTime.Now:yyyy-MM-dd_HH-mm-ss}"; + +// document.GetMetadata().Title = fileName; + +// //document.ShowInCompanion(); + +// return document; +// } + +// private static void ComposeHeader(IContainer container, DateTime date) +// { +// container.Row(row => +// { +// row.RelativeItem().Column(column => +// { +// column.Item().Width(64).Height(64).AlignLeft().Image(Logo); +// }); + +// row.RelativeItem().Column(column => +// { +// column.Item().Text($"Daily raport").AlignCenter().Bold().FontSize(20); +// }); + +// row.RelativeItem().Column(column => +// { +// column.Item().Text($"Date: {date.ToString("dd.MM.yyyy")}").AlignRight(); +// }); +// }); +// } + +// private static void ComposeChart(IContainer container, MeasurementPacket packet) +// { +// container.Svg(size => +// { +// ScottPlot.Plot myPlot = new(); + +// foreach (var data in packet.Measurements) +// { +// var scatter = myPlot.Add.Scatter(packet.Time, data.Data); +// scatter.LegendText = data.Name; +// } + +// myPlot.YLabel(packet.MeasurementName, 12); +// myPlot.XLabel("Time", 12); + +// //myPlot.Axes.SetLimitsY(minY, maxY); +// //myPlot.Axes.SetLimitsY(packet.MinY, packet.MaxY); + +// myPlot.Axes.Bottom.TickLabelStyle.Rotation = -45; +// myPlot.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleRight; + +// // create a manual DateTime tick generator and add ticks +// ScottPlot.TickGenerators.DateTimeManual ticks = new(); + +// // add ticks for Mondays only +// for (int i = 0; i < packet.Time.Length; i++) +// { +// if (i == (packet.Time.Length - 1) || i % 4 == 0) +// { +// DateTime date = packet.Time[i]; +// string label = $"{date:HH}:{date:ss}"; +// ticks.AddMajor(date, label); +// } +// } + +// // tell the horizontal axis to use the custom tick generator +// myPlot.Axes.Bottom.TickGenerator = ticks; + +// myPlot.ShowLegend(); + +// //myPlot.SavePng("demo.png", 100, 100); + +// return myPlot.GetSvgXml((int)size.Width, (int)size.Height); +// }); +// } + +// private static void ComposeContent(IContainer container, IEnumerable packets) +// { +// container.MultiColumn(multiColumn => +// { +// multiColumn.Columns(2); +// multiColumn.Spacing(15); + +// multiColumn.Content() +// .Column(column => +// { +// column.Spacing(15); + +// foreach (var packet in packets) +// { +// column.Item().Text(packet.MeasurementName).AlignCenter().Bold().FontSize(16); + +// column.Item().Text(packet.Description).Justify(); + +// column.Item().AspectRatio(1).Element(x => ComposeChart(x, packet)); + +// column.Item().LineHorizontal(1); +// } +// }); +// }); +// } +//} diff --git a/Services/Raports/Raports.Infrastructure/GlobalUsings.cs b/Services/Raports/Raports.Infrastructure/GlobalUsings.cs index 1f20895..404e8b0 100644 --- a/Services/Raports/Raports.Infrastructure/GlobalUsings.cs +++ b/Services/Raports/Raports.Infrastructure/GlobalUsings.cs @@ -1,11 +1,10 @@ -global using Microsoft.AspNetCore.Builder; +global using Azure.Storage.Blobs; global using Microsoft.AspNetCore.Hosting; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Metadata.Builders; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; -global using QuestPDF.Fluent; global using Raports.Domain.Entities; global using Raports.Infrastructure.Database; global using System.Reflection; \ No newline at end of file diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251027000039_M1.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251027000039_M1.Designer.cs deleted file mode 100644 index b502197..0000000 --- a/Services/Raports/Raports.Infrastructure/Migrations/20251027000039_M1.Designer.cs +++ /dev/null @@ -1,185 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Raports.Infrastructure.Database; - -#nullable disable - -namespace Raports.Infrastructure.Migrations -{ - [DbContext(typeof(RaportsDBContext))] - [Migration("20251027000039_M1")] - partial class M1 - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Raports.Domain.Entities.Period", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); - - b.HasKey("ID"); - - b.ToTable("Periods"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("CreationDate") - .HasColumnType("datetime2") - .HasComment("Date when raport was created"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("PeriodID") - .HasColumnType("int"); - - b.Property("RequestID") - .HasColumnType("int"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.HasKey("ID"); - - b.HasIndex("PeriodID"); - - b.HasIndex("RequestID") - .IsUnique(); - - b.ToTable("Raports"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("EndDate") - .HasColumnType("datetime2") - .HasComment("Raport will be generate to this date"); - - b.Property("PeriodID") - .HasColumnType("int"); - - b.Property("RaportID") - .HasColumnType("int"); - - b.Property("RequestCreationDate") - .HasColumnType("datetime2") - .HasComment("Date when request was created"); - - b.Property("StartDate") - .HasColumnType("datetime2") - .HasComment("Raport will be generate from this date"); - - b.Property("StatusID") - .HasColumnType("int"); - - b.HasKey("ID"); - - b.HasIndex("PeriodID"); - - b.HasIndex("StatusID"); - - b.ToTable("Requests"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.RequestStatus", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("What is exactly happening here"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Generated', 'Pending', etc..."); - - b.HasKey("ID"); - - b.ToTable("RequestStatuses"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => - { - b.HasOne("Raports.Domain.Entities.Period", "Period") - .WithMany() - .HasForeignKey("PeriodID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Raports.Domain.Entities.Request", "Request") - .WithOne("Raport") - .HasForeignKey("Raports.Domain.Entities.Raport", "RequestID"); - - b.Navigation("Period"); - - b.Navigation("Request"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.HasOne("Raports.Domain.Entities.Period", "Period") - .WithMany() - .HasForeignKey("PeriodID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Raports.Domain.Entities.RequestStatus", "Status") - .WithMany() - .HasForeignKey("StatusID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Period"); - - b.Navigation("Status"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.Navigation("Raport") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251027000039_M1.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251027000039_M1.cs deleted file mode 100644 index a820e7a..0000000 --- a/Services/Raports/Raports.Infrastructure/Migrations/20251027000039_M1.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Raports.Infrastructure.Migrations -{ - /// - public partial class M1 : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Periods", - columns: table => new - { - ID = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(max)", nullable: false, comment: "For example: 'Daily', 'Weekly', 'Monthly', etc...") - }, - constraints: table => - { - table.PrimaryKey("PK_Periods", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "RequestStatuses", - columns: table => new - { - ID = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(max)", nullable: false, comment: "For example: 'Generated', 'Pending', etc..."), - Description = table.Column(type: "nvarchar(max)", nullable: false, comment: "What is exactly happening here") - }, - constraints: table => - { - table.PrimaryKey("PK_RequestStatuses", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "Requests", - columns: table => new - { - ID = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RequestCreationDate = table.Column(type: "datetime2", nullable: false, comment: "Date when request was created"), - StartDate = table.Column(type: "datetime2", nullable: false, comment: "Raport will be generate from this date"), - EndDate = table.Column(type: "datetime2", nullable: false, comment: "Raport will be generate to this date"), - StatusID = table.Column(type: "int", nullable: false), - RaportID = table.Column(type: "int", nullable: false), - PeriodID = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Requests", x => x.ID); - table.ForeignKey( - name: "FK_Requests_Periods_PeriodID", - column: x => x.PeriodID, - principalTable: "Periods", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Requests_RequestStatuses_StatusID", - column: x => x.StatusID, - principalTable: "RequestStatuses", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Raports", - columns: table => new - { - ID = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - CreationDate = table.Column(type: "datetime2", nullable: false, comment: "Date when raport was created"), - StartDate = table.Column(type: "datetime2", nullable: false), - EndDate = table.Column(type: "datetime2", nullable: false), - PeriodID = table.Column(type: "int", nullable: false), - RequestID = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Raports", x => x.ID); - table.ForeignKey( - name: "FK_Raports_Periods_PeriodID", - column: x => x.PeriodID, - principalTable: "Periods", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Raports_Requests_RequestID", - column: x => x.RequestID, - principalTable: "Requests", - principalColumn: "ID"); - }); - - migrationBuilder.CreateIndex( - name: "IX_Raports_PeriodID", - table: "Raports", - column: "PeriodID"); - - migrationBuilder.CreateIndex( - name: "IX_Raports_RequestID", - table: "Raports", - column: "RequestID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Requests_PeriodID", - table: "Requests", - column: "PeriodID"); - - migrationBuilder.CreateIndex( - name: "IX_Requests_StatusID", - table: "Requests", - column: "StatusID"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Raports"); - - migrationBuilder.DropTable( - name: "Requests"); - - migrationBuilder.DropTable( - name: "Periods"); - - migrationBuilder.DropTable( - name: "RequestStatuses"); - } - } -} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251029112041_M2.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251029112041_M2.Designer.cs deleted file mode 100644 index da0d014..0000000 --- a/Services/Raports/Raports.Infrastructure/Migrations/20251029112041_M2.Designer.cs +++ /dev/null @@ -1,188 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Raports.Infrastructure.Database; - -#nullable disable - -namespace Raports.Infrastructure.Migrations -{ - [DbContext(typeof(RaportsDBContext))] - [Migration("20251029112041_M2")] - partial class M2 - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Raports.Domain.Entities.Period", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("Hours") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); - - b.HasKey("ID"); - - b.ToTable("Periods"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("CreationDate") - .HasColumnType("datetime2") - .HasComment("Date when raport was created"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("PeriodID") - .HasColumnType("int"); - - b.Property("RequestID") - .HasColumnType("int"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.HasKey("ID"); - - b.HasIndex("PeriodID"); - - b.HasIndex("RequestID") - .IsUnique(); - - b.ToTable("Raports"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("EndDate") - .HasColumnType("datetime2") - .HasComment("Raport will be generate to this date"); - - b.Property("PeriodID") - .HasColumnType("int"); - - b.Property("RaportID") - .HasColumnType("int"); - - b.Property("RequestCreationDate") - .HasColumnType("datetime2") - .HasComment("Date when request was created"); - - b.Property("StartDate") - .HasColumnType("datetime2") - .HasComment("Raport will be generate from this date"); - - b.Property("StatusID") - .HasColumnType("int"); - - b.HasKey("ID"); - - b.HasIndex("PeriodID"); - - b.HasIndex("StatusID"); - - b.ToTable("Requests"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.RequestStatus", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("What is exactly happening here"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Generated', 'Pending', etc..."); - - b.HasKey("ID"); - - b.ToTable("RequestStatuses"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => - { - b.HasOne("Raports.Domain.Entities.Period", "Period") - .WithMany() - .HasForeignKey("PeriodID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Raports.Domain.Entities.Request", "Request") - .WithOne("Raport") - .HasForeignKey("Raports.Domain.Entities.Raport", "RequestID"); - - b.Navigation("Period"); - - b.Navigation("Request"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.HasOne("Raports.Domain.Entities.Period", "Period") - .WithMany() - .HasForeignKey("PeriodID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Raports.Domain.Entities.RequestStatus", "Status") - .WithMany() - .HasForeignKey("StatusID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Period"); - - b.Navigation("Status"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.Navigation("Raport") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251031122552_M3.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251031122552_M3.Designer.cs deleted file mode 100644 index e5ca623..0000000 --- a/Services/Raports/Raports.Infrastructure/Migrations/20251031122552_M3.Designer.cs +++ /dev/null @@ -1,185 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Raports.Infrastructure.Database; - -#nullable disable - -namespace Raports.Infrastructure.Migrations -{ - [DbContext(typeof(RaportsDBContext))] - [Migration("20251031122552_M3")] - partial class M3 - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Raports.Domain.Entities.Period", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); - - b.HasKey("ID"); - - b.ToTable("Periods"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("CreationDate") - .HasColumnType("datetime2") - .HasComment("Date when raport was created"); - - b.Property("EndDate") - .HasColumnType("datetime2"); - - b.Property("PeriodID") - .HasColumnType("int"); - - b.Property("RequestID") - .HasColumnType("int"); - - b.Property("StartDate") - .HasColumnType("datetime2"); - - b.HasKey("ID"); - - b.HasIndex("PeriodID"); - - b.HasIndex("RequestID") - .IsUnique(); - - b.ToTable("Raports"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("EndDate") - .HasColumnType("datetime2") - .HasComment("Raport will be generate to this date"); - - b.Property("PeriodID") - .HasColumnType("int"); - - b.Property("RaportID") - .HasColumnType("int"); - - b.Property("RequestCreationDate") - .HasColumnType("datetime2") - .HasComment("Date when request was created"); - - b.Property("StartDate") - .HasColumnType("datetime2") - .HasComment("Raport will be generate from this date"); - - b.Property("StatusID") - .HasColumnType("int"); - - b.HasKey("ID"); - - b.HasIndex("PeriodID"); - - b.HasIndex("StatusID"); - - b.ToTable("Requests"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.RequestStatus", b => - { - b.Property("ID") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("What is exactly happening here"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Generated', 'Pending', etc..."); - - b.HasKey("ID"); - - b.ToTable("RequestStatuses"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => - { - b.HasOne("Raports.Domain.Entities.Period", "Period") - .WithMany() - .HasForeignKey("PeriodID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Raports.Domain.Entities.Request", "Request") - .WithOne("Raport") - .HasForeignKey("Raports.Domain.Entities.Raport", "RequestID"); - - b.Navigation("Period"); - - b.Navigation("Request"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.HasOne("Raports.Domain.Entities.Period", "Period") - .WithMany() - .HasForeignKey("PeriodID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Raports.Domain.Entities.RequestStatus", "Status") - .WithMany() - .HasForeignKey("StatusID") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Period"); - - b.Navigation("Status"); - }); - - modelBuilder.Entity("Raports.Domain.Entities.Request", b => - { - b.Navigation("Raport") - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251125144217_Initial-Migration.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251125144217_Initial-Migration.Designer.cs new file mode 100644 index 0000000..87d58af --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251125144217_Initial-Migration.Designer.cs @@ -0,0 +1,400 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251125144217_Initial-Migration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.Location", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.HasKey("ID"); + + b.ToTable("Periods"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251125144217_Initial-Migration.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251125144217_Initial-Migration.cs new file mode 100644 index 0000000..16f8d43 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251125144217_Initial-Migration.cs @@ -0,0 +1,316 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Locations", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false, comment: "For example: 'Kitchen', 'Attic' etc...") + }, + constraints: table => + { + table.PrimaryKey("PK_Locations", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "Measurements", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Unit = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Measurements", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "Periods", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false, comment: "For example: 'Daily', 'Weekly', 'Monthly', etc...") + }, + constraints: table => + { + table.PrimaryKey("PK_Periods", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "Statuses", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + Description = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Statuses", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "Raports", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RaportCreationDate = table.Column(type: "datetime2", nullable: false, comment: "Date of Raport creation"), + RaportCompletedDate = table.Column(type: "datetime2", nullable: false, comment: "Date of Raport completion"), + StartDate = table.Column(type: "datetime2", nullable: false, comment: "Date of first measurement"), + EndDate = table.Column(type: "datetime2", nullable: false, comment: "Date of last measurement"), + Message = table.Column(type: "nvarchar(max)", nullable: false), + PeriodID = table.Column(type: "int", nullable: false), + StatusID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Raports", x => x.ID); + table.ForeignKey( + name: "FK_Raports_Periods_PeriodID", + column: x => x.PeriodID, + principalTable: "Periods", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Raports_Statuses_StatusID", + column: x => x.StatusID, + principalTable: "Statuses", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "MeasurementGroups", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Summary = table.Column(type: "nvarchar(max)", nullable: false, comment: "Combined summary for all location groups"), + RaportID = table.Column(type: "int", nullable: false), + MeasurementID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MeasurementGroups", x => x.ID); + table.ForeignKey( + name: "FK_MeasurementGroups_Measurements_MeasurementID", + column: x => x.MeasurementID, + principalTable: "Measurements", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_MeasurementGroups_Raports_RaportID", + column: x => x.RaportID, + principalTable: "Raports", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RequestedLocations", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + LocationID = table.Column(type: "int", nullable: false), + RaportID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RequestedLocations", x => x.ID); + table.ForeignKey( + name: "FK_RequestedLocations_Locations_LocationID", + column: x => x.LocationID, + principalTable: "Locations", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RequestedLocations_Raports_RaportID", + column: x => x.RaportID, + principalTable: "Raports", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RequestedMeasurements", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + MeasurementID = table.Column(type: "int", nullable: false), + RaportID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RequestedMeasurements", x => x.ID); + table.ForeignKey( + name: "FK_RequestedMeasurements_Measurements_MeasurementID", + column: x => x.MeasurementID, + principalTable: "Measurements", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_RequestedMeasurements_Raports_RaportID", + column: x => x.RaportID, + principalTable: "Raports", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "LocationGroups", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Summary = table.Column(type: "nvarchar(max)", nullable: false, comment: "Verbal summary of data stored for this location"), + LocationID = table.Column(type: "int", nullable: false), + MeasurementGroupID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LocationGroups", x => x.ID); + table.ForeignKey( + name: "FK_LocationGroups_Locations_LocationID", + column: x => x.LocationID, + principalTable: "Locations", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_LocationGroups_MeasurementGroups_MeasurementGroupID", + column: x => x.MeasurementGroupID, + principalTable: "MeasurementGroups", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SampleGroups", + columns: table => new + { + ID = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Date = table.Column(type: "datetime2", nullable: false, comment: "Time of measurement"), + Value = table.Column(type: "float", nullable: false, comment: "Value of measurement"), + LocationGroupID = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SampleGroups", x => x.ID); + table.ForeignKey( + name: "FK_SampleGroups_LocationGroups_LocationGroupID", + column: x => x.LocationGroupID, + principalTable: "LocationGroups", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_LocationGroups_LocationID", + table: "LocationGroups", + column: "LocationID"); + + migrationBuilder.CreateIndex( + name: "IX_LocationGroups_MeasurementGroupID", + table: "LocationGroups", + column: "MeasurementGroupID"); + + migrationBuilder.CreateIndex( + name: "IX_MeasurementGroups_MeasurementID", + table: "MeasurementGroups", + column: "MeasurementID"); + + migrationBuilder.CreateIndex( + name: "IX_MeasurementGroups_RaportID", + table: "MeasurementGroups", + column: "RaportID"); + + migrationBuilder.CreateIndex( + name: "IX_Raports_PeriodID", + table: "Raports", + column: "PeriodID"); + + migrationBuilder.CreateIndex( + name: "IX_Raports_StatusID", + table: "Raports", + column: "StatusID"); + + migrationBuilder.CreateIndex( + name: "IX_RequestedLocations_LocationID", + table: "RequestedLocations", + column: "LocationID"); + + migrationBuilder.CreateIndex( + name: "IX_RequestedLocations_RaportID", + table: "RequestedLocations", + column: "RaportID"); + + migrationBuilder.CreateIndex( + name: "IX_RequestedMeasurements_MeasurementID", + table: "RequestedMeasurements", + column: "MeasurementID"); + + migrationBuilder.CreateIndex( + name: "IX_RequestedMeasurements_RaportID", + table: "RequestedMeasurements", + column: "RaportID"); + + migrationBuilder.CreateIndex( + name: "IX_SampleGroups_LocationGroupID", + table: "SampleGroups", + column: "LocationGroupID"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RequestedLocations"); + + migrationBuilder.DropTable( + name: "RequestedMeasurements"); + + migrationBuilder.DropTable( + name: "SampleGroups"); + + migrationBuilder.DropTable( + name: "LocationGroups"); + + migrationBuilder.DropTable( + name: "Locations"); + + migrationBuilder.DropTable( + name: "MeasurementGroups"); + + migrationBuilder.DropTable( + name: "Measurements"); + + migrationBuilder.DropTable( + name: "Raports"); + + migrationBuilder.DropTable( + name: "Periods"); + + migrationBuilder.DropTable( + name: "Statuses"); + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251201115011_#3-Hash-Added-To-Location.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201115011_#3-Hash-Added-To-Location.Designer.cs new file mode 100644 index 0000000..c9dd389 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201115011_#3-Hash-Added-To-Location.Designer.cs @@ -0,0 +1,404 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251201115011_#3-Hash-Added-To-Location")] + partial class _3HashAddedToLocation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.Location", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Hash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.HasKey("ID"); + + b.ToTable("Periods"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251031122552_M3.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201115011_#3-Hash-Added-To-Location.cs similarity index 59% rename from Services/Raports/Raports.Infrastructure/Migrations/20251031122552_M3.cs rename to Services/Raports/Raports.Infrastructure/Migrations/20251201115011_#3-Hash-Added-To-Location.cs index 053b1dc..f6ad0d0 100644 --- a/Services/Raports/Raports.Infrastructure/Migrations/20251031122552_M3.cs +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201115011_#3-Hash-Added-To-Location.cs @@ -5,25 +5,25 @@ namespace Raports.Infrastructure.Migrations { /// - public partial class M3 : Migration + public partial class _3HashAddedToLocation : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "Hours", - table: "Periods"); + migrationBuilder.AddColumn( + name: "Hash", + table: "Locations", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "Hours", - table: "Periods", - type: "int", - nullable: false, - defaultValue: 0); + migrationBuilder.DropColumn( + name: "Hash", + table: "Locations"); } } } diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251201115049_#4-Hash-Added-To-Location.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201115049_#4-Hash-Added-To-Location.Designer.cs new file mode 100644 index 0000000..6834262 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201115049_#4-Hash-Added-To-Location.Designer.cs @@ -0,0 +1,404 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251201115049_#4-Hash-Added-To-Location")] + partial class _4HashAddedToLocation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.Location", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Hash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.HasKey("ID"); + + b.ToTable("Periods"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251029112041_M2.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201115049_#4-Hash-Added-To-Location.cs similarity index 52% rename from Services/Raports/Raports.Infrastructure/Migrations/20251029112041_M2.cs rename to Services/Raports/Raports.Infrastructure/Migrations/20251201115049_#4-Hash-Added-To-Location.cs index 4c97ad5..9c65bfc 100644 --- a/Services/Raports/Raports.Infrastructure/Migrations/20251029112041_M2.cs +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201115049_#4-Hash-Added-To-Location.cs @@ -5,25 +5,18 @@ namespace Raports.Infrastructure.Migrations { /// - public partial class M2 : Migration + public partial class _4HashAddedToLocation : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "Hours", - table: "Periods", - type: "int", - nullable: false, - defaultValue: 0); + } /// protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "Hours", - table: "Periods"); + } } } diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251201131451_#5-BatchFrame-Added-To-Period.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201131451_#5-BatchFrame-Added-To-Period.Designer.cs new file mode 100644 index 0000000..a7b1b83 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201131451_#5-BatchFrame-Added-To-Period.Designer.cs @@ -0,0 +1,417 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251201131451_#5-BatchFrame-Added-To-Period")] + partial class _5BatchFrameAddedToPeriod + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.Location", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Hash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MaxAcceptableMissingTimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.Property("TimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("time") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.HasKey("ID"); + + b.ToTable("Periods", t => + { + t.HasCheckConstraint("CK_Period_MaxAcceptableMissingTimeFrame_Range", "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + }); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251201131451_#5-BatchFrame-Added-To-Period.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201131451_#5-BatchFrame-Added-To-Period.cs new file mode 100644 index 0000000..5aaf982 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201131451_#5-BatchFrame-Added-To-Period.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + /// + public partial class _5BatchFrameAddedToPeriod : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxAcceptableMissingTimeFrame", + table: "Periods", + type: "int", + nullable: false, + defaultValue: 1); + + migrationBuilder.AddColumn( + name: "TimeFrame", + table: "Periods", + type: "time", + nullable: false, + defaultValue: new TimeSpan(0, 0, 0, 0, 0)); + + migrationBuilder.AddCheckConstraint( + name: "CK_Period_MaxAcceptableMissingTimeFrame_Range", + table: "Periods", + sql: "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropCheckConstraint( + name: "CK_Period_MaxAcceptableMissingTimeFrame_Range", + table: "Periods"); + + migrationBuilder.DropColumn( + name: "MaxAcceptableMissingTimeFrame", + table: "Periods"); + + migrationBuilder.DropColumn( + name: "TimeFrame", + table: "Periods"); + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251201212019_#6-BatchFrame-Added-To-Period.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201212019_#6-BatchFrame-Added-To-Period.Designer.cs new file mode 100644 index 0000000..d13bec0 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201212019_#6-BatchFrame-Added-To-Period.Designer.cs @@ -0,0 +1,416 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251201212019_#6-BatchFrame-Added-To-Period")] + partial class _6BatchFrameAddedToPeriod + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.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("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MaxAcceptableMissingTimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.Property("TimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("time") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.HasKey("ID"); + + b.ToTable("Periods", t => + { + t.HasCheckConstraint("CK_Period_MaxAcceptableMissingTimeFrame_Range", "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + }); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251201212019_#6-BatchFrame-Added-To-Period.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251201212019_#6-BatchFrame-Added-To-Period.cs new file mode 100644 index 0000000..b3fef4a --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251201212019_#6-BatchFrame-Added-To-Period.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + /// + public partial class _6BatchFrameAddedToPeriod : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Hash", + table: "Locations", + type: "uniqueidentifier", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Hash", + table: "Locations", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uniqueidentifier"); + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251202131955_#7-DocumentHash-Added-To-Raport-Entity.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251202131955_#7-DocumentHash-Added-To-Raport-Entity.Designer.cs new file mode 100644 index 0000000..e214e4a --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251202131955_#7-DocumentHash-Added-To-Raport-Entity.Designer.cs @@ -0,0 +1,422 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251202131955_#7-DocumentHash-Added-To-Raport-Entity")] + partial class _7DocumentHashAddedToRaportEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.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("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MaxAcceptableMissingTimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.Property("TimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("time") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.HasKey("ID"); + + b.ToTable("Periods", t => + { + t.HasCheckConstraint("CK_Period_MaxAcceptableMissingTimeFrame_Range", "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + }); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DocumentHash") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("00000000-0000-0000-0000-000000000000")) + .HasComment("Hash that allows to identify PDF in Azure Blob Storage"); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251202131955_#7-DocumentHash-Added-To-Raport-Entity.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251202131955_#7-DocumentHash-Added-To-Raport-Entity.cs new file mode 100644 index 0000000..86a44e8 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251202131955_#7-DocumentHash-Added-To-Raport-Entity.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + /// + public partial class _7DocumentHashAddedToRaportEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DocumentHash", + table: "Raports", + type: "uniqueidentifier", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + comment: "Hash that allows to identify PDF in Azure Blob Storage"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DocumentHash", + table: "Raports"); + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251202135124_#8-Min-And-Min-Chart-Values-For-Chart.Designer.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251202135124_#8-Min-And-Min-Chart-Values-For-Chart.Designer.cs new file mode 100644 index 0000000..3bb2ffb --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251202135124_#8-Min-And-Min-Chart-Values-For-Chart.Designer.cs @@ -0,0 +1,428 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Raports.Infrastructure.Database; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + [DbContext(typeof(RaportsDBContext))] + [Migration("20251202135124_#8-Min-And-Min-Chart-Values-For-Chart")] + partial class _8MinAndMinChartValuesForChart + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Raports.Domain.Entities.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("For example: 'Kitchen', 'Attic' etc..."); + + b.HasKey("ID"); + + b.ToTable("Locations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("MeasurementGroupID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MaxChartYValue") + .HasColumnType("int"); + + b.Property("MinChartYValue") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Period", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MaxAcceptableMissingTimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.Property("TimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("time") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.HasKey("ID"); + + b.ToTable("Periods", t => + { + t.HasCheckConstraint("CK_Period_MaxAcceptableMissingTimeFrame_Range", "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + }); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DocumentHash") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("00000000-0000-0000-0000-000000000000")) + .HasComment("Hash that allows to identify PDF in Azure Blob Storage"); + + b.Property("EndDate") + .HasColumnType("datetime2") + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PeriodID") + .HasColumnType("int"); + + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); + + b.Property("RaportCreationDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport creation"); + + b.Property("StartDate") + .HasColumnType("datetime2") + .HasComment("Date of first measurement"); + + b.Property("StatusID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("PeriodID"); + + b.HasIndex("StatusID"); + + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Status", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("Statuses"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("MeasurementGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.HasOne("Raports.Domain.Entities.Period", "Period") + .WithMany() + .HasForeignKey("PeriodID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Period"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/20251202135124_#8-Min-And-Min-Chart-Values-For-Chart.cs b/Services/Raports/Raports.Infrastructure/Migrations/20251202135124_#8-Min-And-Min-Chart-Values-For-Chart.cs new file mode 100644 index 0000000..33c2bb6 --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Migrations/20251202135124_#8-Min-And-Min-Chart-Values-For-Chart.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Raports.Infrastructure.Migrations +{ + /// + public partial class _8MinAndMinChartValuesForChart : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxChartYValue", + table: "Measurements", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "MinChartYValue", + table: "Measurements", + type: "int", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxChartYValue", + table: "Measurements"); + + migrationBuilder.DropColumn( + name: "MinChartYValue", + table: "Measurements"); + } + } +} diff --git a/Services/Raports/Raports.Infrastructure/Migrations/RaportsDBContextModelSnapshot.cs b/Services/Raports/Raports.Infrastructure/Migrations/RaportsDBContextModelSnapshot.cs index fad000a..0515a02 100644 --- a/Services/Raports/Raports.Infrastructure/Migrations/RaportsDBContextModelSnapshot.cs +++ b/Services/Raports/Raports.Infrastructure/Migrations/RaportsDBContextModelSnapshot.cs @@ -22,7 +22,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Raports.Domain.Entities.Period", b => + modelBuilder.Entity("Raports.Domain.Entities.Location", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -30,17 +30,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + b.Property("Hash") + .HasColumnType("uniqueidentifier"); + b.Property("Name") .IsRequired() .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + .HasComment("For example: 'Kitchen', 'Attic' etc..."); b.HasKey("ID"); - b.ToTable("Periods"); + b.ToTable("Locations"); }); - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -48,33 +51,82 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - b.Property("CreationDate") - .HasColumnType("datetime2") - .HasComment("Date when raport was created"); + b.Property("LocationID") + .HasColumnType("int"); - b.Property("EndDate") - .HasColumnType("datetime2"); + b.Property("MeasurementGroupID") + .HasColumnType("int"); - b.Property("PeriodID") + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Verbal summary of data stored for this location"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("MeasurementGroupID"); + + b.ToTable("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Measurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() .HasColumnType("int"); - b.Property("RequestID") + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MaxChartYValue") .HasColumnType("int"); - b.Property("StartDate") - .HasColumnType("datetime2"); + b.Property("MinChartYValue") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Unit") + .IsRequired() + .HasColumnType("nvarchar(max)"); b.HasKey("ID"); - b.HasIndex("PeriodID"); + b.ToTable("Measurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); - b.HasIndex("RequestID") - .IsUnique(); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); - b.ToTable("Raports"); + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("Combined summary for all location groups"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("MeasurementGroups"); }); - modelBuilder.Entity("Raports.Domain.Entities.Request", b => + modelBuilder.Entity("Raports.Domain.Entities.Period", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -82,23 +134,65 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + b.Property("MaxAcceptableMissingTimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("For example: 'Daily', 'Weekly', 'Monthly', etc..."); + + b.Property("TimeFrame") + .ValueGeneratedOnAdd() + .HasColumnType("time") + .HasDefaultValue(new TimeSpan(0, 0, 0, 0, 0)); + + b.HasKey("ID"); + + b.ToTable("Periods", t => + { + t.HasCheckConstraint("CK_Period_MaxAcceptableMissingTimeFrame_Range", "MaxAcceptableMissingTimeFrame >= 1 AND MaxAcceptableMissingTimeFrame <= 100"); + }); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("DocumentHash") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("00000000-0000-0000-0000-000000000000")) + .HasComment("Hash that allows to identify PDF in Azure Blob Storage"); + b.Property("EndDate") .HasColumnType("datetime2") - .HasComment("Raport will be generate to this date"); + .HasComment("Date of last measurement"); + + b.Property("Message") + .IsRequired() + .HasColumnType("nvarchar(max)"); b.Property("PeriodID") .HasColumnType("int"); - b.Property("RaportID") - .HasColumnType("int"); + b.Property("RaportCompletedDate") + .HasColumnType("datetime2") + .HasComment("Date of Raport completion"); - b.Property("RequestCreationDate") + b.Property("RaportCreationDate") .HasColumnType("datetime2") - .HasComment("Date when request was created"); + .HasComment("Date of Raport creation"); b.Property("StartDate") .HasColumnType("datetime2") - .HasComment("Raport will be generate from this date"); + .HasComment("Date of first measurement"); b.Property("StatusID") .HasColumnType("int"); @@ -109,10 +203,82 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("StatusID"); - b.ToTable("Requests"); + b.ToTable("Raports"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("LocationID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("LocationID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedLocations"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("MeasurementID") + .HasColumnType("int"); + + b.Property("RaportID") + .HasColumnType("int"); + + b.HasKey("ID"); + + b.HasIndex("MeasurementID"); + + b.HasIndex("RaportID"); + + b.ToTable("RequestedMeasurements"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("Time of measurement"); + + b.Property("LocationGroupID") + .HasColumnType("int"); + + b.Property("Value") + .HasColumnType("float") + .HasComment("Value of measurement"); + + b.HasKey("ID"); + + b.HasIndex("LocationGroupID"); + + b.ToTable("SampleGroups"); }); - modelBuilder.Entity("Raports.Domain.Entities.RequestStatus", b => + modelBuilder.Entity("Raports.Domain.Entities.Status", b => { b.Property("ID") .ValueGeneratedOnAdd() @@ -122,37 +288,56 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Description") .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("What is exactly happening here"); + .HasColumnType("nvarchar(max)"); b.Property("Name") .IsRequired() - .HasColumnType("nvarchar(max)") - .HasComment("For example: 'Generated', 'Pending', etc..."); + .HasColumnType("nvarchar(max)"); b.HasKey("ID"); - b.ToTable("RequestStatuses"); + b.ToTable("Statuses"); }); - modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => { - b.HasOne("Raports.Domain.Entities.Period", "Period") + b.HasOne("Raports.Domain.Entities.Location", "Location") .WithMany() - .HasForeignKey("PeriodID") + .HasForeignKey("LocationID") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Raports.Domain.Entities.Request", "Request") - .WithOne("Raport") - .HasForeignKey("Raports.Domain.Entities.Raport", "RequestID"); + b.HasOne("Raports.Domain.Entities.MeasurementGroup", "MeasurementGroup") + .WithMany("LocationGroups") + .HasForeignKey("MeasurementGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("Period"); + b.Navigation("Location"); - b.Navigation("Request"); + b.Navigation("MeasurementGroup"); }); - modelBuilder.Entity("Raports.Domain.Entities.Request", b => + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany("MeasurementGroups") + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => { b.HasOne("Raports.Domain.Entities.Period", "Period") .WithMany() @@ -160,7 +345,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Raports.Domain.Entities.RequestStatus", "Status") + b.HasOne("Raports.Domain.Entities.Status", "Status") .WithMany() .HasForeignKey("StatusID") .OnDelete(DeleteBehavior.Cascade) @@ -171,10 +356,68 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Status"); }); - modelBuilder.Entity("Raports.Domain.Entities.Request", b => + modelBuilder.Entity("Raports.Domain.Entities.RequestedLocation", b => { - b.Navigation("Raport") + b.HasOne("Raports.Domain.Entities.Location", "Location") + .WithMany() + .HasForeignKey("LocationID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.RequestedMeasurement", b => + { + b.HasOne("Raports.Domain.Entities.Measurement", "Measurement") + .WithMany() + .HasForeignKey("MeasurementID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Raports.Domain.Entities.Raport", "Raport") + .WithMany() + .HasForeignKey("RaportID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Measurement"); + + b.Navigation("Raport"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.SampleGroup", b => + { + b.HasOne("Raports.Domain.Entities.LocationGroup", "LocationGroup") + .WithMany("SampleGroups") + .HasForeignKey("LocationGroupID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LocationGroup"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.LocationGroup", b => + { + b.Navigation("SampleGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.MeasurementGroup", b => + { + b.Navigation("LocationGroups"); + }); + + modelBuilder.Entity("Raports.Domain.Entities.Raport", b => + { + b.Navigation("MeasurementGroups"); }); #pragma warning restore 612, 618 } diff --git a/Services/Raports/Raports.Infrastructure/Raports.Infrastructure.csproj b/Services/Raports/Raports.Infrastructure/Raports.Infrastructure.csproj index dc3b045..e7aecd4 100644 --- a/Services/Raports/Raports.Infrastructure/Raports.Infrastructure.csproj +++ b/Services/Raports/Raports.Infrastructure/Raports.Infrastructure.csproj @@ -8,14 +8,13 @@ + - - + - @@ -39,6 +38,7 @@ + diff --git a/Services/Raports/Raports.Infrastructure/Scripts/S_001_Initial-Migration.sql b/Services/Raports/Raports.Infrastructure/Scripts/S_001_Initial-Migration.sql new file mode 100644 index 0000000..804d21b --- /dev/null +++ b/Services/Raports/Raports.Infrastructure/Scripts/S_001_Initial-Migration.sql @@ -0,0 +1,67 @@ +-- Clear database and reseed tables. +delete from [Raports]; +delete from [MeasurementGroups]; +delete from [LocationGroups]; +delete from [SampleGroups]; +delete from [RequestedLocations]; +delete from [RequestedMeasurements]; +delete from [Measurements]; +delete from [Locations]; +delete from [Periods]; +delete from [Statuses]; + +-- Create 'Locations' seed +INSERT INTO Locations (Name, Hash) +VALUES +('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 'Statuses' seed +INSERT INTO Statuses (Name, Description) +VALUES +('Suspended', 'Waiting for user interaction'), +('Pending', 'This raport is waiting for its computation'), +('Processing', 'This raport is being computated'), +('Completed', 'Raport was created successfully'), +('Failed', 'Failed to generate raport'), +('Deleted Room', 'This raport was deleted'); + +-- Create 'Periods' seed +INSERT INTO Periods (Name, TimeFrame, MaxAcceptableMissingTimeFrame) +VALUES +('Hourly', '00:01:00.0000000', 5), +('Daily', '01:00:00.0000000', 5), +('Weekly', '23:59:59.9999999', 5), +('Monthly', '23:59:59.9999999', 5); + +-- Create 'Measurements' seed +INSERT INTO Measurements (Name, Unit, MinChartYValue, MaxChartYValue) +VALUES +('Air Temperature', '°C', 10, 40), +('Relative Humidity', '%', 20, 70), +('Carbon Dioxide', 'ppm', 400, 2000), +('Volatile Organic Compounds', 'ppb', 0, 500), +('Particulate Matter 1um', 'µg/m³', 0, 150), +('Particulate Matter 2.5um', 'µg/m³', 0, 150), +('Particulate Matter 10um', 'µg/m³', 0, 200), +('Formaldehyde', 'ppb', 0, 100), +('Carbon Monoxide', 'ppm', 0, 20), +('Ozone', 'ppb', 0, 100), +('Ammonia', 'ppm', 0, 5), +('Air Flow Rate', 'm³/h', 0, 1500), +('Air Ionization Level', 'ions/cm³', 0, 50000), +('Oxygen Concentration', '%', 15, 25), +('Radon Concentration', 'Bq/m³', 0, 200), +('Illuminance level', 'lux', 0, 2000), +('Sound Pressure Level', 'dB', 20, 80); diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 42e4e10..6bbb927 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -38,8 +38,8 @@ services: - ASPNETCORE_HTTP_PORTS=8080 - ASPNETCORE_HTTPS_PORTS=8081 ports: - - "6003:8080" - - "6063:8081" + - "6002:8080" + - "6062:8081" volumes: - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro @@ -66,8 +66,8 @@ services: - ASPNETCORE_HTTP_PORTS=8080 - ASPNETCORE_HTTPS_PORTS=8081 ports: - - "6006:8080" - - "6066:8081" + - "6003:8080" + - "6063:8081" depends_on: - raports.db volumes: