diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs index d4491cc..99e2d8c 100644 --- a/Persistence.API/Controllers/TimeSeriesController.cs +++ b/Persistence.API/Controllers/TimeSeriesController.cs @@ -32,9 +32,11 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi GetResampledData(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024) + [HttpGet("resampled")] + public async Task GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024) { - throw new NotImplementedException(); + var result = await this.timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount); + return Ok(result); } [HttpPost] diff --git a/Persistence.API/Startup.cs b/Persistence.API/Startup.cs index 32466c8..e074845 100644 --- a/Persistence.API/Startup.cs +++ b/Persistence.API/Startup.cs @@ -33,6 +33,11 @@ public class Startup app.UseRouting(); + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + app.UseAuthentication(); app.UseAuthorization(); diff --git a/Persistence.Database.Postgres/Migrations/20241118091712_InitialCreate.Designer.cs b/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs similarity index 92% rename from Persistence.Database.Postgres/Migrations/20241118091712_InitialCreate.Designer.cs rename to Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs index b340413..17ec6e1 100644 --- a/Persistence.Database.Postgres/Migrations/20241118091712_InitialCreate.Designer.cs +++ b/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ using Persistence.Database.Model; namespace Persistence.Database.Postgres.Migrations { [DbContext(typeof(PersistenceDbContext))] - [Migration("20241118091712_InitialCreate")] + [Migration("20241122074646_InitialCreate")] partial class InitialCreate { /// @@ -29,12 +29,9 @@ namespace Persistence.Database.Postgres.Migrations modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); b.Property("AxialLoad") .HasColumnType("double precision") @@ -52,10 +49,6 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("double precision") .HasColumnName("blockSpeed"); - b.Property("Date") - .HasColumnType("timestamp with time zone") - .HasColumnName("date"); - b.Property("Flow") .HasColumnType("double precision") .HasColumnName("flow"); @@ -112,7 +105,7 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("double precision") .HasColumnName("wellDepth"); - b.HasKey("Id"); + b.HasKey("Date"); b.ToTable("DataSaub"); }); diff --git a/Persistence.Database.Postgres/Migrations/20241118091712_InitialCreate.cs b/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.cs similarity index 89% rename from Persistence.Database.Postgres/Migrations/20241118091712_InitialCreate.cs rename to Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.cs index 61ea137..dfa9396 100644 --- a/Persistence.Database.Postgres/Migrations/20241118091712_InitialCreate.cs +++ b/Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.cs @@ -1,6 +1,5 @@ using System; using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -19,8 +18,6 @@ namespace Persistence.Database.Postgres.Migrations name: "DataSaub", columns: table => new { - id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), date = table.Column(type: "timestamp with time zone", nullable: false), mode = table.Column(type: "integer", nullable: true), user = table.Column(type: "text", nullable: true), @@ -43,7 +40,7 @@ namespace Persistence.Database.Postgres.Migrations }, constraints: table => { - table.PrimaryKey("PK_DataSaub", x => x.id); + table.PrimaryKey("PK_DataSaub", x => x.date); }); } diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs index 08c2720..394c112 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs @@ -26,12 +26,9 @@ namespace Persistence.Database.Postgres.Migrations modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); b.Property("AxialLoad") .HasColumnType("double precision") @@ -49,10 +46,6 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("double precision") .HasColumnName("blockSpeed"); - b.Property("Date") - .HasColumnType("timestamp with time zone") - .HasColumnName("date"); - b.Property("Flow") .HasColumnType("double precision") .HasColumnName("flow"); @@ -109,7 +102,7 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("double precision") .HasColumnName("wellDepth"); - b.HasKey("Id"); + b.HasKey("Date"); b.ToTable("DataSaub"); }); diff --git a/Persistence.Database/Entity/DataSaub.cs b/Persistence.Database/Entity/DataSaub.cs index 39b56cb..fecda5a 100644 --- a/Persistence.Database/Entity/DataSaub.cs +++ b/Persistence.Database/Entity/DataSaub.cs @@ -1,12 +1,11 @@ -using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Persistence.Database.Model; public class DataSaub : ITimestampedData { - [Column("id")] - public int Id { get; set; } - - [Column("date")] + [Key, Column("date")] public DateTimeOffset Date { get; set; } [Column("mode")] diff --git a/Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs b/Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs index 03b7638..4030d25 100644 --- a/Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs +++ b/Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs @@ -1,13 +1,22 @@ using Microsoft.AspNetCore.Mvc; +using Persistence.Models; using Refit; namespace Persistence.IntegrationTests.Clients; public interface ITimeSeriesClient where TDto : class, new() { - [Post("/api/dataSaub")] - Task> InsertRangeAsync(IEnumerable dtos); + private const string BaseRoute = "/api/dataSaub"; - [Get("/api/dataSaub")] - Task>> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd); + [Post($"{BaseRoute}")] + Task> InsertRange(IEnumerable dtos); + + [Get($"{BaseRoute}")] + Task>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd); + + [Get($"{BaseRoute}/resampled")] + Task>> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024); + + [Get($"{BaseRoute}/datesRange")] + Task> GetDatesRange(); } diff --git a/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs b/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs index 0e15a60..ae19a8a 100644 --- a/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs @@ -14,7 +14,6 @@ public class DataSaubControllerTest : TimeSeriesBaseControllerTest : BaseIntegrationTest - where TEntity : class + where TEntity : class, ITimestampedData, new() where TDto : class, new() { private ITimeSeriesClient client; @@ -26,7 +18,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat Task.Run(async () => { client = await factory.GetAuthorizedHttpClient>(string.Empty); - }).Wait(); + }).Wait(); } public async Task InsertRangeSuccess(TDto dto) @@ -35,7 +27,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat var expected = dto.Adapt(); //act - var response = await client.InsertRangeAsync(new TDto[] { expected }); + var response = await client.InsertRange(new TDto[] { expected }); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -51,11 +43,80 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat dbContext.SaveChanges(); - var response = await client.GetAsync(beginDate, endDate); + var response = await client.Get(beginDate, endDate); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(response.Content); Assert.Single(response.Content); } + + public async Task GetDatesRangeSuccess(TEntity entity) + { + //arrange + var datesRangeExpected = 30; + + var entity2 = entity.Adapt(); + entity2.Date = entity.Date.AddDays(datesRangeExpected); + + var dbset = dbContext.Set(); + dbset.Add(entity); + dbset.Add(entity2); + + dbContext.SaveChanges(); + + var response = await client.GetDatesRange(); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + + var datesRangeActual = (response.Content.To - response.Content.From).Days; + Assert.Equal(datesRangeExpected, datesRangeActual); + } + + public async Task GetResampledDataSuccess(TEntity entity) + { + //arrange + var approxPointsCount = 10; + var differenceBetweenStartAndEndDays = 50; + + var entities = new List(); + for (var i = 1; i <= differenceBetweenStartAndEndDays; i++) + { + var entity2 = entity.Adapt(); + entity2.Date = entity.Date.AddDays(i - 1); + + entities.Add(entity2); + } + + var dbset = dbContext.Set(); + dbset.AddRange(entities); + + dbContext.SaveChanges(); + + var response = await client.GetResampledData(entity.Date.AddMinutes(-1), differenceBetweenStartAndEndDays * 24 * 60 * 60 + 60, approxPointsCount); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + + var ratio = entities.Count() / approxPointsCount; + if (ratio > 1) + { + var expectedResampledCount = entities + .Where((_, index) => index % ratio == 0) + .Count(); + + Assert.Equal(expectedResampledCount, response.Content.Count()); + } + else + { + Assert.Equal(entities.Count(), response.Content.Count()); + } + + + } + + } diff --git a/Persistence.Repository/Data/DataSaubDto.cs b/Persistence.Repository/Data/DataSaubDto.cs index bb9f74f..518380b 100644 --- a/Persistence.Repository/Data/DataSaubDto.cs +++ b/Persistence.Repository/Data/DataSaubDto.cs @@ -4,8 +4,6 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Persistence.Repository.Data; public class DataSaubDto : ITimeSeriesAbstractDto { - public int Id { get; set; } - public DateTimeOffset Date { get; set; } = DateTimeOffset.UtcNow; public int? Mode { get; set; } diff --git a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs index 7c9d06e..52a580c 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs @@ -77,5 +77,25 @@ public class TimeSeriesDataCachedRepository : TimeSeriesDataRepos }; }); } + + public override async Task> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024) + { + var dtos = LastData.Where(i => i.Date >= dateBegin); + if (LastData.Count == 0 || LastData[0].Date > dateBegin) + { + dtos = await base.GetGtDate(dateBegin, CancellationToken.None); + } + + var dateEnd = dateBegin.AddSeconds(intervalSec); + dtos = dtos + .Where(i => i.Date <= dateEnd); + + var ratio = dtos.Count() / approxPointsCount; + if (ratio > 1) + dtos = dtos + .Where((_, index) => index % ratio == 0); + + return dtos; + } } diff --git a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs index ec255cb..7db1193 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs @@ -84,9 +84,26 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository return dto; } - public Task> GetResampledData(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024) + public virtual Task> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024) { - //todo - throw new NotImplementedException(); + //if (!caches.TryGetValue(idTelemetry, out TelemetryDataCacheItem? cacheItem)) + // return null; + + //var cacheLastData = cacheItem.LastData; + + //if (cacheLastData.Count == 0 || cacheLastData[0].DateTime > dateBegin) + // return null; + + //var dateEnd = dateBegin.AddSeconds(intervalSec); + //var items = cacheLastData + // .Where(i => i.DateTime >= dateBegin && i.DateTime <= dateEnd); + + //var ratio = items.Count() / approxPointsCount; + //if (ratio > 1) + // items = items + // .Where((_, index) => index % ratio == 0); + + //return items; + return null; } } diff --git a/Persistence/API/ITimeSeriesBaseDataApi.cs b/Persistence/API/ITimeSeriesBaseDataApi.cs index 16b7f2b..dcce959 100644 --- a/Persistence/API/ITimeSeriesBaseDataApi.cs +++ b/Persistence/API/ITimeSeriesBaseDataApi.cs @@ -15,7 +15,7 @@ public interface ITimeSeriesBaseDataApi /// дата окончания /// /// - Task GetResampledData(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024); + Task GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024); /// /// Получить диапазон дат, для которых есть данные в репозитории diff --git a/Persistence/Repositories/ITimeSeriesBaseRepository.cs b/Persistence/Repositories/ITimeSeriesBaseRepository.cs index 5e99582..b9cf358 100644 --- a/Persistence/Repositories/ITimeSeriesBaseRepository.cs +++ b/Persistence/Repositories/ITimeSeriesBaseRepository.cs @@ -15,7 +15,7 @@ public interface ITimeSeriesBaseRepository /// дата окончания /// /// - Task> GetResampledData(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024); + Task> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024); /// /// Получить диапазон дат, для которых есть данные в репозитории