diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs index e6c74ef..a4a860a 100644 --- a/Persistence.API/Controllers/TimeSeriesController.cs +++ b/Persistence.API/Controllers/TimeSeriesController.cs @@ -19,24 +19,32 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + public async Task Get(DateTimeOffset dateBegin, CancellationToken token) { - var result = await this.timeSeriesDataRepository.GetAsync(dateBegin, dateEnd, token); + var result = await this.timeSeriesDataRepository.GetGtDate(dateBegin, token); return Ok(result); - } [HttpGet("datesRange")] - public async Task GetDatesRangeAsync(CancellationToken token) + public async Task GetDatesRange(CancellationToken token) { - var result = await this.timeSeriesDataRepository.GetDatesRangeAsync(token); + var result = await this.timeSeriesDataRepository.GetDatesRange(token); + return Ok(result); + } + + [HttpGet("resampled")] + public async Task GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) + { + var result = await this.timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token); return Ok(result); } [HttpPost] - public async Task InsertRangeAsync(IEnumerable dtos, CancellationToken token) + public async Task InsertRange(IEnumerable dtos, CancellationToken token) { var result = await this.timeSeriesDataRepository.InsertRange(dtos, token); return Ok(result); } + + } diff --git a/Persistence.API/Program.cs b/Persistence.API/Program.cs index 93e62a1..bc54d92 100644 --- a/Persistence.API/Program.cs +++ b/Persistence.API/Program.cs @@ -1,10 +1,13 @@ +using Persistence.Models; + namespace Persistence.API; public class Program { public static void Main(string[] args) { + var host = CreateHostBuilder(args).Build(); Startup.BeforeRunHandler(host); host.Run(); 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.API/appsettings.Tests.json b/Persistence.API/appsettings.Tests.json index c1329f9..033464f 100644 --- a/Persistence.API/appsettings.Tests.json +++ b/Persistence.API/appsettings.Tests.json @@ -4,5 +4,12 @@ "Port": 5432, "Username": "postgres", "Password": "q" - } + }, + "KeycloakTestUser": { + "username": "myuser", + "password": 12345, + "clientId": "webapi", + "grantType": "password" + }, + "KeycloakGetTokenUrl": "http://192.168.0.10:8321/realms/Persistence/protocol/openid-connect/token" } diff --git a/Persistence.API/appsettings.json b/Persistence.API/appsettings.json index 2e0033b..3e1eea7 100644 --- a/Persistence.API/appsettings.json +++ b/Persistence.API/appsettings.json @@ -10,9 +10,9 @@ }, "AllowedHosts": "*", "Authentication": { - "MetadataAddress": "http://localhost:8080/realms/TestRealm/.well-known/openid-configuration", + "MetadataAddress": "http://192.168.0.10:8321/realms/Persistence/.well-known/openid-configuration", "Audience": "account", - "ValidIssuer": "http://localhost:8080/realms/TestRealm", - "AuthorizationUrl": "http://localhost:8080/realms/TestRealm/protocol/openid-connect/auth" + "ValidIssuer": "http://192.168.0.10:8321/realms/Persistence", + "AuthorizationUrl": "http://192.168.0.10:8321/realms/Persistence/protocol/openid-connect/auth" } } 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 59686f7..f41f669 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 025492a..ae19a8a 100644 --- a/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs @@ -11,10 +11,9 @@ public class DataSaubControllerTest : TimeSeriesBaseControllerTest : BaseIntegrationTest - where TEntity : class + where TEntity : class, ITimestampedData, new() where TDto : class, new() { private ITimeSeriesClient client; @@ -22,7 +14,11 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat public TimeSeriesBaseControllerTest(WebAppFactoryFixture factory) : base(factory) { dbContext.CleanupDbSet(); - client = factory.GetHttpClient>(string.Empty); + + Task.Run(async () => + { + client = await factory.GetAuthorizedHttpClient>(string.Empty); + }).Wait(); } public async Task InsertRangeSuccess(TDto dto) @@ -31,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); @@ -47,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.IntegrationTests/JwtToken.cs b/Persistence.IntegrationTests/JwtToken.cs new file mode 100644 index 0000000..38bf315 --- /dev/null +++ b/Persistence.IntegrationTests/JwtToken.cs @@ -0,0 +1,8 @@ +using System.Text.Json.Serialization; + +namespace Persistence.IntegrationTests; +public class JwtToken +{ + [JsonPropertyName("access_token")] + public required string AccessToken { get; set; } +} diff --git a/Persistence.IntegrationTests/KeyCloakUser.cs b/Persistence.IntegrationTests/KeyCloakUser.cs new file mode 100644 index 0000000..aa4d335 --- /dev/null +++ b/Persistence.IntegrationTests/KeyCloakUser.cs @@ -0,0 +1,27 @@ +namespace Persistence.IntegrationTests; + +/// +/// настройки credentials для пользователя в KeyCloak +/// +public class KeyCloakUser +{ + /// + /// + /// + public required string Username { get; set; } + + /// + /// + /// + public required string Password { get; set; } + + /// + /// + /// + public required string ClientId { get; set; } + + /// + /// + /// + public required string GrantType { get; set; } +} diff --git a/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj b/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj index 912eeda..f36e78d 100644 --- a/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj +++ b/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj @@ -15,6 +15,7 @@ + all diff --git a/Persistence.IntegrationTests/WebAppFactoryFixture.cs b/Persistence.IntegrationTests/WebAppFactoryFixture.cs index 802c384..57e0dfc 100644 --- a/Persistence.IntegrationTests/WebAppFactoryFixture.cs +++ b/Persistence.IntegrationTests/WebAppFactoryFixture.cs @@ -3,10 +3,12 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Persistence.API; using Persistence.Database.Model; using Persistence.Database.Postgres; -using Persistence.API; using Refit; +using RestSharp; +using System.Net.Http.Headers; using System.Text.Json; using Persistence.Database.Postgres; @@ -23,6 +25,8 @@ public class WebAppFactoryFixture : WebApplicationFactory private static readonly RefitSettings RefitSettings = new(new SystemTextJsonContentSerializer(JsonSerializerOptions)); private readonly string connectionString; + private readonly KeyCloakUser keycloakTestUser; + public readonly string KeycloakGetTokenUrl; public WebAppFactoryFixture() { @@ -32,6 +36,10 @@ public class WebAppFactoryFixture : WebApplicationFactory var dbConnection = configuration.GetSection("DbConnection").Get()!; connectionString = dbConnection.GetConnectionString(); + + keycloakTestUser = configuration.GetSection("KeycloakTestUser").Get()!; + + KeycloakGetTokenUrl = configuration.GetSection("KeycloakGetTokenUrl").Value!; } protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -53,7 +61,6 @@ public class WebAppFactoryFixture : WebApplicationFactory var dbContext = scopedServices.GetRequiredService(); dbContext.Database.EnsureCreatedAndMigrated(); - //dbContext.Deposits.AddRange(Data.Defaults.Deposits); dbContext.SaveChanges(); }); } @@ -80,23 +87,44 @@ public class WebAppFactoryFixture : WebApplicationFactory return RestService.For(httpClient, RefitSettings); } - //public T GetAuthorizedHttpClient(string uriSuffix) - //{ - // var httpClient = GetAuthorizedHttpClient(); - // if (string.IsNullOrEmpty(uriSuffix)) - // return RestService.For(httpClient, RefitSettings); + public async Task GetAuthorizedHttpClient(string uriSuffix) + { + var httpClient = await GetAuthorizedHttpClient(); + if (string.IsNullOrEmpty(uriSuffix)) + return RestService.For(httpClient, RefitSettings); - // if (httpClient.BaseAddress is not null) - // httpClient.BaseAddress = new Uri(httpClient.BaseAddress, uriSuffix); + if (httpClient.BaseAddress is not null) + httpClient.BaseAddress = new Uri(httpClient.BaseAddress, uriSuffix); - // return RestService.For(httpClient, RefitSettings); - //} + return RestService.For(httpClient, RefitSettings); + } - //private HttpClient GetAuthorizedHttpClient() - //{ - // var httpClient = CreateClient(); - // var jwtToken = ApiTokenHelper.GetAdminUserToken(); - // httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken); - // return httpClient; - //} + private async Task GetAuthorizedHttpClient() + { + var httpClient = CreateClient(); + var token = await GetTokenAsync(); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + return httpClient; + } + + private async Task GetTokenAsync() + { + var restClient = new RestClient(); + + var request = new RestRequest(KeycloakGetTokenUrl, Method.Post); + request.AddParameter("username", keycloakTestUser.Username); + request.AddParameter("password", keycloakTestUser.Password); + request.AddParameter("client_id", keycloakTestUser.ClientId); + request.AddParameter("grant_type", keycloakTestUser.GrantType); + + var keyCloackResponse = await restClient.PostAsync(request); + if (keyCloackResponse.IsSuccessful && !String.IsNullOrEmpty(keyCloackResponse.Content)) + { + var token = JsonSerializer.Deserialize(keyCloackResponse.Content)!; + return token.AccessToken; + } + + return String.Empty; + } } 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 4a553ac..20dbe00 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs @@ -10,8 +10,8 @@ public class TimeSeriesDataCachedRepository : TimeSeriesDataRepos where TEntity : class, ITimestampedData, new() where TDto : class, ITimeSeriesAbstractDto, new() { - public static TDto FirstByDate { get; set; } = default!; - public static CyclicArray LastData { get; set; } = null!; + public static TDto? FirstByDate { get; private set; } + public static CyclicArray LastData { get; } = new CyclicArray(CacheItemsCount); private const int CacheItemsCount = 3600; @@ -19,8 +19,6 @@ public class TimeSeriesDataCachedRepository : TimeSeriesDataRepos { Task.Run(async () => { - LastData = new CyclicArray(CacheItemsCount); - var firstDateItem = await base.GetFirstAsync(CancellationToken.None); if (firstDateItem == null) { @@ -35,17 +33,17 @@ public class TimeSeriesDataCachedRepository : TimeSeriesDataRepos }).Wait(); } - public override async Task> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + public override async Task> GetGtDate(DateTimeOffset dateBegin, CancellationToken token) { if (LastData.Count() == 0 || LastData[0].Date > dateBegin) { - var dtos = await base.GetAsync(dateBegin, dateEnd, token); + var dtos = await base.GetGtDate(dateBegin, token); return dtos; } var items = LastData - .Where(i => i.Date >= dateBegin && i.Date <= dateEnd); + .Where(i => i.Date >= dateBegin); return items; } @@ -64,5 +62,44 @@ public class TimeSeriesDataCachedRepository : TimeSeriesDataRepos return result; } + + public override async Task GetDatesRange(CancellationToken token) + { + if (FirstByDate == null) + return null; + + return await Task.Run(() => + { + return new DatesRangeDto + { + From = FirstByDate.Date, + To = LastData[^1].Date + }; + }); + } + + public override async Task> GetResampledData( + DateTimeOffset dateBegin, + double intervalSec = 600d, + int approxPointsCount = 1024, + CancellationToken token = default) + { + var dtos = LastData.Where(i => i.Date >= dateBegin); + if (LastData.Count == 0 || LastData[0].Date > dateBegin) + { + dtos = await base.GetGtDate(dateBegin, token); + } + + 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 9b339a5..d73caf7 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs @@ -18,16 +18,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository protected virtual IQueryable GetQueryReadOnly() => this.db.Set(); - public virtual async Task> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) - { - var query = GetQueryReadOnly(); - var entities = await query.ToArrayAsync(token); - var dtos = entities.Select(e => e.Adapt()); - - return dtos; - } - - public virtual async Task GetDatesRangeAsync(CancellationToken token) + public virtual async Task GetDatesRange(CancellationToken token) { var query = GetQueryReadOnly(); var minDate = await query.MinAsync(o => o.Date, token); @@ -60,7 +51,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository return result; } - public async Task> GetLastAsync(int takeCount, CancellationToken token) + protected async Task> GetLastAsync(int takeCount, CancellationToken token) { var query = GetQueryReadOnly() .OrderByDescending(e => e.Date) @@ -72,17 +63,37 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository return dtos; } - public async Task GetFirstAsync(CancellationToken token) + protected async Task GetFirstAsync(CancellationToken token) { var query = GetQueryReadOnly() .OrderBy(e => e.Date); var entity = await query.FirstOrDefaultAsync(token); - if(entity == null) + if (entity == null) return null; var dto = entity.Adapt(); return dto; } + + public async virtual Task> GetResampledData( + DateTimeOffset dateBegin, + double intervalSec = 600d, + int approxPointsCount = 1024, + CancellationToken token = default) + { + var dtos = await GetGtDate(dateBegin, token); + + 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/API/IChangeLogApi.cs b/Persistence/API/IChangeLogApi.cs index 716944e..bb4a3c3 100644 --- a/Persistence/API/IChangeLogApi.cs +++ b/Persistence/API/IChangeLogApi.cs @@ -31,7 +31,7 @@ public interface IChangeLogApi /// /// /// - Task> AddAsync(TDto dto, CancellationToken token); + Task> Add(TDto dto, CancellationToken token); /// /// Добавить несколько записей @@ -39,7 +39,7 @@ public interface IChangeLogApi /// /// /// - Task> AddRangeAsync(IEnumerable dtos, CancellationToken token); + Task> AddRange(IEnumerable dtos, CancellationToken token); /// /// Обновить одну запись @@ -47,7 +47,7 @@ public interface IChangeLogApi /// /// /// - Task> UpdateAsync(TDto dto, CancellationToken token); + Task> Update(TDto dto, CancellationToken token); /// /// Обновить несколько записей @@ -55,7 +55,7 @@ public interface IChangeLogApi /// /// /// - Task> UpdateRangeAsync(IEnumerable dtos, CancellationToken token); + Task> UpdateRange(IEnumerable dtos, CancellationToken token); /// /// Удалить одну запись @@ -63,7 +63,7 @@ public interface IChangeLogApi /// /// /// - Task> DeleteAsync(int id, CancellationToken token); + Task> Delete(int id, CancellationToken token); /// /// Удалить несколько записей @@ -71,5 +71,5 @@ public interface IChangeLogApi /// /// /// - Task> DeleteRangeAsync(IEnumerable ids, CancellationToken token); + Task> DeleteRange(IEnumerable ids, CancellationToken token); } diff --git a/Persistence/API/IDictionaryElementApi.cs b/Persistence/API/IDictionaryElementApi.cs index 540d454..fac7870 100644 --- a/Persistence/API/IDictionaryElementApi.cs +++ b/Persistence/API/IDictionaryElementApi.cs @@ -13,7 +13,7 @@ public interface IDictionaryElementApi where TDto : class, new() /// ключ справочника /// /// - Task>> GetAsync(Guid dictionaryKey, CancellationToken token); + Task>> Get(Guid dictionaryKey, CancellationToken token); /// /// Добавить элемент в справочник @@ -22,7 +22,7 @@ public interface IDictionaryElementApi where TDto : class, new() /// /// /// - Task> AddAsync(Guid dictionaryKey, TDto dto, CancellationToken token); + Task> Add(Guid dictionaryKey, TDto dto, CancellationToken token); /// /// Изменить одну запись @@ -32,7 +32,7 @@ public interface IDictionaryElementApi where TDto : class, new() /// /// /// - Task> UpdateAsync(Guid dictionaryKey, Guid dictionaryElementKey, TDto dto, CancellationToken token); + Task> Update(Guid dictionaryKey, Guid dictionaryElementKey, TDto dto, CancellationToken token); /// /// Удалить одну запись @@ -41,5 +41,5 @@ public interface IDictionaryElementApi where TDto : class, new() /// ключ элемента в справочнике /// /// - Task> DeleteAsync(Guid dictionaryKey, Guid dictionaryElementKey, CancellationToken token); + Task> Delete(Guid dictionaryKey, Guid dictionaryElementKey, CancellationToken token); } diff --git a/Persistence/API/ISyncApi.cs b/Persistence/API/ISyncApi.cs index 9df4301..7f72812 100644 --- a/Persistence/API/ISyncApi.cs +++ b/Persistence/API/ISyncApi.cs @@ -15,7 +15,7 @@ public interface ISyncApi where TDto : class, new() /// количество записей /// /// - Task>> GetPartAsync(DateTimeOffset dateBegin, int take = 24 * 60 * 60, CancellationToken token = default); + Task>> GetPart(DateTimeOffset dateBegin, int take = 24 * 60 * 60, CancellationToken token = default); /// /// Получить диапазон дат, для которых есть данные в репозитории diff --git a/Persistence/API/ITableDataApi.cs b/Persistence/API/ITableDataApi.cs index ae7099c..a310799 100644 --- a/Persistence/API/ITableDataApi.cs +++ b/Persistence/API/ITableDataApi.cs @@ -19,5 +19,5 @@ public interface ITableDataApi /// параметры фильтрации /// /// - Task>> GetPageAsync(TRequest request, CancellationToken token); + Task>> GetPage(TRequest request, CancellationToken token); } diff --git a/Persistence/API/IGraphDataApi.cs b/Persistence/API/ITimeSeriesBaseDataApi.cs similarity index 57% rename from Persistence/API/IGraphDataApi.cs rename to Persistence/API/ITimeSeriesBaseDataApi.cs index b609f2f..480f145 100644 --- a/Persistence/API/IGraphDataApi.cs +++ b/Persistence/API/ITimeSeriesBaseDataApi.cs @@ -4,23 +4,27 @@ using Persistence.Models; namespace Persistence.API; /// -/// Интерфейс для работы с API графиков +/// Базовый интерфейс для работы с временными рядами /// -public interface IGraphDataApi +public interface ITimeSeriesBaseDataApi { /// - /// Получить список объектов с прореживанием, удовлетворящий диапазону дат + /// Получить список объектов с прореживанием, удовлетворяющий диапазону дат /// /// дата начала /// дата окончания /// /// - Task>> GetThinnedDataAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024); + Task GetResampledData( + DateTimeOffset dateBegin, + double intervalSec = 600d, + int approxPointsCount = 1024, + CancellationToken token = default); /// /// Получить диапазон дат, для которых есть данные в репозитории /// /// /// - Task> GetDatesRangeAsync(CancellationToken token); + Task GetDatesRange(CancellationToken token); } diff --git a/Persistence/API/ITimeSeriesDataApi.cs b/Persistence/API/ITimeSeriesDataApi.cs index a6a0363..a2406aa 100644 --- a/Persistence/API/ITimeSeriesDataApi.cs +++ b/Persistence/API/ITimeSeriesDataApi.cs @@ -11,24 +11,16 @@ namespace Persistence.API; /// /// Интерфейс для работы с API временных данных /// -public interface ITimeSeriesDataApi +public interface ITimeSeriesDataApi : ITimeSeriesBaseDataApi where TDto : class, ITimeSeriesAbstractDto, new() { /// /// Получить список объектов, удовлетворяющий диапазон дат /// /// дата начала - /// дата окончания /// /// - Task GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token); - - /// - /// Получить диапазон дат, для которых есть данные в репозитории - /// - /// - /// - Task GetDatesRangeAsync(CancellationToken token); + Task Get(DateTimeOffset dateBegin, CancellationToken token); /// /// Добавление записей @@ -36,7 +28,7 @@ public interface ITimeSeriesDataApi /// /// /// - Task InsertRangeAsync(IEnumerable dtos, CancellationToken token); + Task InsertRange(IEnumerable dtos, CancellationToken token); } diff --git a/Persistence/Models/ChangeLogDto.cs b/Persistence/Models/ChangeLogDto.cs index 5d5e7f5..6b2a090 100644 --- a/Persistence/Models/ChangeLogDto.cs +++ b/Persistence/Models/ChangeLogDto.cs @@ -26,7 +26,7 @@ public class ChangeLogDto where T: class public DateTimeOffset Creation { get; set; } /// - /// Дата устаревания (например при удалении) + /// Дата устаревания (например, при удалении) /// public DateTimeOffset? Obsolete { get; set; } diff --git a/Persistence/Repositories/ITableDataRepository.cs b/Persistence/Repositories/ITableDataRepository.cs index 22ec55c..73d332d 100644 --- a/Persistence/Repositories/ITableDataRepository.cs +++ b/Persistence/Repositories/ITableDataRepository.cs @@ -15,5 +15,5 @@ public interface ITableDataRepository /// параметры фильтрации /// /// - Task> GetAsync(TRequest request, CancellationToken token); + Task> Get(TRequest request, CancellationToken token); } diff --git a/Persistence/Repositories/IGraphDataRepository.cs b/Persistence/Repositories/ITimeSeriesBaseRepository.cs similarity index 69% rename from Persistence/Repositories/IGraphDataRepository.cs rename to Persistence/Repositories/ITimeSeriesBaseRepository.cs index d110ffa..7102d97 100644 --- a/Persistence/Repositories/IGraphDataRepository.cs +++ b/Persistence/Repositories/ITimeSeriesBaseRepository.cs @@ -5,22 +5,25 @@ namespace Persistence.Repositories; /// /// Интерфейс по работе с прореженными данными /// -public interface IGraphDataRepository +public interface ITimeSeriesBaseRepository where TDto : class, new() { /// /// Получить список объектов с прореживанием /// /// дата начала - /// дата окончания /// /// - Task> GetThinnedDataAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024); + Task> GetResampledData( + DateTimeOffset dateBegin, + double intervalSec = 600d, + int approxPointsCount = 1024, + CancellationToken token = default); /// /// Получить диапазон дат, для которых есть данные в репозитории /// /// /// - Task GetDatesRangeAsync(CancellationToken token); + Task GetDatesRange(CancellationToken token); } diff --git a/Persistence/Repositories/ITimeSeriesDataRepository.cs b/Persistence/Repositories/ITimeSeriesDataRepository.cs index f3e49eb..9de7fc7 100644 --- a/Persistence/Repositories/ITimeSeriesDataRepository.cs +++ b/Persistence/Repositories/ITimeSeriesDataRepository.cs @@ -6,24 +6,9 @@ namespace Persistence.Repositories; /// Интерфейс по работе с временными данными /// /// -public interface ITimeSeriesDataRepository : ISyncRepository +public interface ITimeSeriesDataRepository : ISyncRepository, ITimeSeriesBaseRepository where TDto : class, ITimeSeriesAbstractDto, new() { - /// - /// Получить страницу списка объектов - /// - /// дата начала - /// дата окончания - /// - /// - Task> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token); - - /// - /// Получить диапазон дат, для которых есть данные в репозитории - /// - /// - /// - Task GetDatesRangeAsync(CancellationToken token); /// /// Добавление записей /// @@ -31,19 +16,4 @@ public interface ITimeSeriesDataRepository : ISyncRepository /// /// Task InsertRange(IEnumerable dtos, CancellationToken token); - - /// - /// Получение списка последних записей - /// - /// количество записей - /// - /// - Task> GetLastAsync(int takeCount, CancellationToken token); - - /// - /// Получение первой записи - /// - /// - /// - Task GetFirstAsync(CancellationToken token); }