From efed33e6486ec657d08eef95975078afcef1c43b Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Mon, 2 Dec 2024 15:14:00 +0500 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B2=20Set?= =?UTF-8?q?point=20=D0=B8=20TechMessage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DataSaubController.cs | 2 +- .../Controllers/SetpointController.cs | 38 +++++- .../Controllers/TechMessagesController.cs | 73 ++++++++--- .../Controllers/TimeSeriesController.cs | 16 +-- Persistence.Client/Clients/ISetpointClient.cs | 6 + .../Clients/ITechMessagesClient.cs | 13 +- Persistence.Client/Helpers/ApiTokenHelper.cs | 2 + .../20241118052225_SetpointMigration.cs | 2 +- ...02072250_TechMessageMigration.Designer.cs} | 12 +- ...=> 20241202072250_TechMessageMigration.cs} | 10 +- .../PersistenceDbContextModelSnapshot.cs | 10 +- .../Entity/{ADSystem.cs => DrillingSystem.cs} | 2 +- Persistence.Database/Entity/Setpoint.cs | 2 +- Persistence.Database/Entity/TechMessage.cs | 2 +- .../Controllers/SetpointControllerTest.cs | 67 ++++++++++ .../Controllers/TechMessagesControllerTest.cs | 94 ++++++++++++-- .../Repositories/SetpointRepository.cs | 34 ++++- .../Repositories/TechMessagesRepository.cs | 117 +++++++++++++----- Persistence/API/ISetpointApi.cs | 18 +-- Persistence/API/ISyncApi.cs | 2 +- .../{ADSystemDto.cs => DrillingSystemDto.cs} | 2 +- Persistence/Models/MessagesStatisticDto.cs | 17 +++ Persistence/Models/SetpointLogDto.cs | 2 +- .../Repositories/ISetpointRepository.cs | 39 ++++-- .../Repositories/ITechMessagesRepository.cs | 20 ++- 25 files changed, 482 insertions(+), 120 deletions(-) rename Persistence.Database.Postgres/Migrations/{20241128074729_TechMessageMigration.Designer.cs => 20241202072250_TechMessageMigration.Designer.cs} (96%) rename Persistence.Database.Postgres/Migrations/{20241128074729_TechMessageMigration.cs => 20241202072250_TechMessageMigration.cs} (91%) rename Persistence.Database/Entity/{ADSystem.cs => DrillingSystem.cs} (95%) rename Persistence/Models/{ADSystemDto.cs => DrillingSystemDto.cs} (92%) create mode 100644 Persistence/Models/MessagesStatisticDto.cs diff --git a/Persistence.API/Controllers/DataSaubController.cs b/Persistence.API/Controllers/DataSaubController.cs index 437aee3..202e527 100644 --- a/Persistence.API/Controllers/DataSaubController.cs +++ b/Persistence.API/Controllers/DataSaubController.cs @@ -6,7 +6,7 @@ using Persistence.Repository.Data; namespace Persistence.API.Controllers; /// -/// +/// Работа с временными данными /// [ApiController] [Authorize] diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs index 5a5cb52..108c3ef 100644 --- a/Persistence.API/Controllers/SetpointController.cs +++ b/Persistence.API/Controllers/SetpointController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using System.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Persistence.Models; using Persistence.Repositories; @@ -63,18 +64,47 @@ public class SetpointController : ControllerBase, ISetpointApi return Ok(result); } + /// + /// Получить диапазон дат, для которых есть данные в репозитории + /// + /// + /// + [HttpGet("range")] + public async Task> GetDatesRangeAsync(CancellationToken token) + { + var result = await setpointRepository.GetDatesRangeAsync(token); + + return Ok(result); + } + + /// + /// Получить порцию записей, начиная с заданной даты + /// + /// + /// + /// + /// + [HttpGet("part")] + public async Task>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token) + { + var result = await setpointRepository.GetPart(dateBegin, take, token); + + return Ok(result); + } + /// /// Сохранить уставку /// /// /// + /// /// /// [HttpPost] - public async Task> Save(Guid setpointKey, object newValue, CancellationToken token) + [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] + public async Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token) { - // ToDo: вычитка idUser - await setpointRepository.Save(setpointKey, newValue, 0, token); + await setpointRepository.Save(setpointKey, newValue, idUser, token); return Ok(); } diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index fb3315e..4e91802 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using System.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Persistence.Models; using Persistence.Repositories; @@ -14,6 +15,14 @@ namespace Persistence.API.Controllers; public class TechMessagesController : ControllerBase { private readonly ITechMessagesRepository techMessagesRepository; + private static readonly Dictionary categories = new Dictionary() + { + { 0, "System" }, + { 1, "Авария" }, + { 2, "Предупреждение" }, + { 3, "Инфо" }, + { 4, "Прочее" } + }; public TechMessagesController(ITechMessagesRepository techMessagesRepository) { @@ -37,14 +46,14 @@ public class TechMessagesController : ControllerBase /// /// Получить статистику по системам /// - /// /// + /// /// /// - [HttpGet("statistics/{autoDrillingSystem}")] - public async Task> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token) + [HttpGet("statistics")] + public async Task>> GetStatistics([FromQuery] IEnumerable autoDrillingSystem, [FromQuery] IEnumerable categoryIds, CancellationToken token) { - var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); + var result = await techMessagesRepository.GetStatistics(autoDrillingSystem, categoryIds, token); return Ok(result); } @@ -55,13 +64,41 @@ public class TechMessagesController : ControllerBase /// /// [HttpGet("systems")] - public async Task>> GetSystems(CancellationToken token) + public async Task>> GetSystems(CancellationToken token) { var result = await techMessagesRepository.GetSystems(token); return Ok(result); } + /// + /// Получить диапазон дат, для которых есть данные в репозитории + /// + /// + /// + [HttpGet("range")] + public async Task> GetDatesRangeAsync(CancellationToken token) + { + var result = await techMessagesRepository.GetDatesRangeAsync(token); + + return Ok(result); + } + + /// + /// Получить порцию записей, начиная с заданной даты + /// + /// + /// + /// + /// + [HttpGet("part")] + public async Task>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token) + { + var result = await techMessagesRepository.GetPart(dateBegin, take, token); + + return Ok(result); + } + /// /// Добавить новые технологические сообщения /// @@ -69,9 +106,16 @@ public class TechMessagesController : ControllerBase /// /// [HttpPost] - public async Task> InsertRange([FromBody] IEnumerable dtos, CancellationToken token) + [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] + public async Task InsertRange([FromBody] IEnumerable dtos, CancellationToken token) { - var result = await techMessagesRepository.InsertRange(dtos, token); + var userId = User.GetUserId(); + foreach (var dto in dtos) + { + dto.UserId = userId; + } + + var result = await techMessagesRepository.InsertRange(dtos, token); return CreatedAtAction(nameof(InsertRange), result); } @@ -83,15 +127,6 @@ public class TechMessagesController : ControllerBase [HttpGet("categories")] public ActionResult> GetImportantCategories() { - var result = new Dictionary() - { - { 0, "System" }, - { 1, "Авария" }, - { 2, "Предупреждение" }, - { 3, "Инфо" }, - { 4, "Прочее" } - }; - - return Ok(result); + return Ok(categories); } -} +} \ No newline at end of file diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs index a4f00f3..90c78c8 100644 --- a/Persistence.API/Controllers/TimeSeriesController.cs +++ b/Persistence.API/Controllers/TimeSeriesController.cs @@ -19,7 +19,7 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi - /// , + /// Получить список объектов, удовлетворяющий диапазону дат /// /// /// @@ -28,24 +28,24 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi Get(DateTimeOffset dateBegin, CancellationToken token) { - var result = await this.timeSeriesDataRepository.GetGtDate(dateBegin, token); + var result = await timeSeriesDataRepository.GetGtDate(dateBegin, token); return Ok(result); } /// - /// , + /// Получить диапазон дат, для которых есть данные в репозиторие /// /// /// [HttpGet("datesRange")] public async Task GetDatesRange(CancellationToken token) { - var result = await this.timeSeriesDataRepository.GetDatesRange(token); + var result = await timeSeriesDataRepository.GetDatesRange(token); return Ok(result); } /// - /// , + /// Получить список объектов с прореживанием, удовлетворяющий диапазону дат /// /// /// @@ -55,12 +55,12 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) { - var result = await this.timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token); + var result = await timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token); return Ok(result); } /// - /// + /// Добавить записи /// /// /// @@ -68,7 +68,7 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi InsertRange(IEnumerable dtos, CancellationToken token) { - var result = await this.timeSeriesDataRepository.InsertRange(dtos, token); + var result = await timeSeriesDataRepository.InsertRange(dtos, token); return Ok(result); } diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs index 72d76e1..886e034 100644 --- a/Persistence.Client/Clients/ISetpointClient.cs +++ b/Persistence.Client/Clients/ISetpointClient.cs @@ -19,6 +19,12 @@ public interface ISetpointClient [Get($"{BaseRoute}/log")] Task>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable setpointKeys); + [Get($"{BaseRoute}/range")] + Task> GetDatesRangeAsync(CancellationToken token); + + [Get($"{BaseRoute}/part")] + Task>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token); + [Post($"{BaseRoute}/")] Task Save(Guid setpointKey, object newValue); } diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs index 43839fe..cbdf7ef 100644 --- a/Persistence.Client/Clients/ITechMessagesClient.cs +++ b/Persistence.Client/Clients/ITechMessagesClient.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Persistence.Models; +using Persistence.Models; using Refit; namespace Persistence.Client.Clients @@ -20,7 +19,13 @@ namespace Persistence.Client.Clients [Get($"{BaseRoute}/systems")] Task>> GetSystems(CancellationToken token); - [Get($"{BaseRoute}/statistics/" + "{autoDrillingSystem}")] - Task> GetStatistics(string? autoDrillingSystem, int? importantId, CancellationToken token); + [Get($"{BaseRoute}/range")] + Task> GetDatesRangeAsync(CancellationToken token); + + [Get($"{BaseRoute}/part")] + Task>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token); + + [Get($"{BaseRoute}/statistics")] + Task>> GetStatistics([Query] string autoDrillingSystem, [Query] int categoryId, CancellationToken token); } } diff --git a/Persistence.Client/Helpers/ApiTokenHelper.cs b/Persistence.Client/Helpers/ApiTokenHelper.cs index e508922..5eed66e 100644 --- a/Persistence.Client/Helpers/ApiTokenHelper.cs +++ b/Persistence.Client/Helpers/ApiTokenHelper.cs @@ -29,8 +29,10 @@ public static class ApiTokenHelper private static string CreateDefaultJwtToken(this AuthUser authUser) { + var nameIdetifier = Guid.NewGuid().ToString(); var claims = new List() { + new(ClaimTypes.NameIdentifier, nameIdetifier), new("client_id", authUser.ClientId), new("username", authUser.Username), new("password", authUser.Password), diff --git a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs index 49e438a..ea6fccf 100644 --- a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs +++ b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs @@ -18,7 +18,7 @@ namespace Persistence.Database.Postgres.Migrations Key = table.Column(type: "uuid", nullable: false, comment: "Ключ"), Created = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата изменения уставки"), Value = table.Column(type: "jsonb", nullable: false, comment: "Значение уставки"), - IdUser = table.Column(type: "integer", nullable: false, comment: "Id автора последнего изменения") + IdUser = table.Column(type: "uuid", nullable: false, comment: "Id автора последнего изменения") }, constraints: table => { diff --git a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.Designer.cs similarity index 96% rename from Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs rename to Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.Designer.cs index 6678078..6ed33f7 100644 --- a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs +++ b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.Designer.cs @@ -12,7 +12,7 @@ using Persistence.Database.Model; namespace Persistence.Database.Postgres.Migrations { [DbContext(typeof(PersistenceDbContext))] - [Migration("20241128074729_TechMessageMigration")] + [Migration("20241202072250_TechMessageMigration")] partial class TechMessageMigration { /// @@ -27,7 +27,7 @@ namespace Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b => + modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b => { b.Property("SystemId") .ValueGeneratedOnAdd() @@ -45,7 +45,7 @@ namespace Persistence.Database.Postgres.Migrations b.HasKey("SystemId"); - b.ToTable("ADSystem"); + b.ToTable("DrillingSystem"); }); modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => @@ -203,8 +203,8 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("timestamp with time zone") .HasComment("Дата создания уставки"); - b.Property("IdUser") - .HasColumnType("integer") + b.Property("IdUser") + .HasColumnType("uuid") .HasComment("Id автора последнего изменения"); b.Property("Value") @@ -219,7 +219,7 @@ namespace Persistence.Database.Postgres.Migrations modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => { - b.HasOne("Persistence.Database.Entity.ADSystem", "System") + b.HasOne("Persistence.Database.Entity.DrillingSystem", "System") .WithMany() .HasForeignKey("SystemId") .OnDelete(DeleteBehavior.Cascade) diff --git a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.cs similarity index 91% rename from Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs rename to Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.cs index 047ad52..ccf18d4 100644 --- a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs +++ b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.cs @@ -12,7 +12,7 @@ namespace Persistence.Database.Postgres.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "ADSystem", + name: "DrillingSystem", columns: table => new { SystemId = table.Column(type: "uuid", nullable: false, comment: "Id системы автобурения"), @@ -21,7 +21,7 @@ namespace Persistence.Database.Postgres.Migrations }, constraints: table => { - table.PrimaryKey("PK_ADSystem", x => x.SystemId); + table.PrimaryKey("PK_DrillingSystem", x => x.SystemId); }); migrationBuilder.CreateTable( @@ -40,9 +40,9 @@ namespace Persistence.Database.Postgres.Migrations { table.PrimaryKey("PK_TechMessage", x => x.EventId); table.ForeignKey( - name: "FK_TechMessage_ADSystem_SystemId", + name: "FK_TechMessage_DrillingSystem_SystemId", column: x => x.SystemId, - principalTable: "ADSystem", + principalTable: "DrillingSystem", principalColumn: "SystemId", onDelete: ReferentialAction.Cascade); }); @@ -60,7 +60,7 @@ namespace Persistence.Database.Postgres.Migrations name: "TechMessage"); migrationBuilder.DropTable( - name: "ADSystem"); + name: "DrillingSystem"); } } } diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs index f5533cc..e53c81a 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs @@ -24,7 +24,7 @@ namespace Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b => + modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b => { b.Property("SystemId") .ValueGeneratedOnAdd() @@ -42,7 +42,7 @@ namespace Persistence.Database.Postgres.Migrations b.HasKey("SystemId"); - b.ToTable("ADSystem"); + b.ToTable("DrillingSystem"); }); modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => @@ -200,8 +200,8 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("timestamp with time zone") .HasComment("Дата создания уставки"); - b.Property("IdUser") - .HasColumnType("integer") + b.Property("IdUser") + .HasColumnType("uuid") .HasComment("Id автора последнего изменения"); b.Property("Value") @@ -216,7 +216,7 @@ namespace Persistence.Database.Postgres.Migrations modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => { - b.HasOne("Persistence.Database.Entity.ADSystem", "System") + b.HasOne("Persistence.Database.Entity.DrillingSystem", "System") .WithMany() .HasForeignKey("SystemId") .OnDelete(DeleteBehavior.Cascade) diff --git a/Persistence.Database/Entity/ADSystem.cs b/Persistence.Database/Entity/DrillingSystem.cs similarity index 95% rename from Persistence.Database/Entity/ADSystem.cs rename to Persistence.Database/Entity/DrillingSystem.cs index 525134c..6588fb0 100644 --- a/Persistence.Database/Entity/ADSystem.cs +++ b/Persistence.Database/Entity/DrillingSystem.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; namespace Persistence.Database.Entity; -public class ADSystem +public class DrillingSystem { [Key, Comment("Id системы автобурения")] public Guid SystemId { get; set; } diff --git a/Persistence.Database/Entity/Setpoint.cs b/Persistence.Database/Entity/Setpoint.cs index ef6b5dc..6ca2c27 100644 --- a/Persistence.Database/Entity/Setpoint.cs +++ b/Persistence.Database/Entity/Setpoint.cs @@ -16,6 +16,6 @@ namespace Persistence.Database.Model public DateTimeOffset Created { get; set; } [Comment("Id автора последнего изменения")] - public int IdUser { get; set; } + public Guid IdUser { get; set; } } } diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs index eacc234..ea29cc2 100644 --- a/Persistence.Database/Entity/TechMessage.cs +++ b/Persistence.Database/Entity/TechMessage.cs @@ -25,7 +25,7 @@ namespace Persistence.Database.Entity public required Guid SystemId { get; set; } [Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")] - public virtual required ADSystem System { get; set; } + public virtual required DrillingSystem System { get; set; } [Comment("Id пользователя за пультом бурильщика")] public Guid UserId { get; set; } diff --git a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs index faa0147..d314cec 100644 --- a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Persistence.Client; using Persistence.Client.Clients; +using Persistence.Database.Model; using Xunit; namespace Persistence.IntegrationTests.Controllers @@ -131,6 +132,72 @@ namespace Persistence.IntegrationTests.Controllers Assert.Equal(setpointKey, response.Content.FirstOrDefault().Key); } + [Fact] + public async Task GetDatesRange_returns_success() + { + //arrange + dbContext.CleanupDbSet(); + + //act + var response = await setpointClient.GetDatesRangeAsync(new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.Equal(DateTimeOffset.MinValue, response.Content?.From); + Assert.Equal(DateTimeOffset.MaxValue, response.Content?.To); + } + + [Fact] + public async Task GetDatesRange_AfterSave_returns_success() + { + //arrange + dbContext.CleanupDbSet(); + await Save(); + + //act + var response = await setpointClient.GetDatesRangeAsync(new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.NotNull(response.Content?.From); + Assert.NotNull(response.Content?.To); + } + + [Fact] + public async Task GetPart_returns_success() + { + //arrange + var dateBegin = DateTimeOffset.UtcNow; + var take = 2; + + //act + var response = await setpointClient.GetPart(dateBegin, take, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.Empty(response.Content); + } + + [Fact] + public async Task GetPart_AfterSave_returns_success() + { + //arrange + var dateBegin = DateTimeOffset.UtcNow; + var take = 1; + await Save(); + + //act + var response = await setpointClient.GetPart(dateBegin, take, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.NotEmpty(response.Content); + } + [Fact] public async Task Save_returns_success() { diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs index 3902681..ec4e40a 100644 --- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs @@ -11,7 +11,7 @@ namespace Persistence.IntegrationTests.Controllers { public class TechMessagesControllerTest : BaseIntegrationTest { - private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey"; + private static readonly string SystemCacheKey = $"{typeof(Database.Entity.DrillingSystem).FullName}CacheKey"; private readonly ITechMessagesClient techMessagesClient; private readonly IMemoryCache memoryCache; public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory) @@ -28,7 +28,10 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetPage_returns_success() { //arrange + memoryCache.Remove(SystemCacheKey); dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + var requestDto = new RequestDto() { Skip = 1, @@ -104,8 +107,9 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetSystems_returns_success() { //arrange - dbContext.CleanupDbSet(); memoryCache.Remove(SystemCacheKey); + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); //act var response = await techMessagesClient.GetSystems(new CancellationToken()); @@ -140,7 +144,10 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetStatistics_returns_success() { //arrange + memoryCache.Remove(SystemCacheKey); dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + var imortantId = 1; var autoDrillingSystem = nameof(TechMessageDto.System); @@ -149,7 +156,8 @@ namespace Persistence.IntegrationTests.Controllers //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(0, response.Content); + Assert.NotNull(response.Content); + Assert.Empty(response.Content); } [Fact] @@ -159,19 +167,89 @@ namespace Persistence.IntegrationTests.Controllers var imortantId = 0; var autoDrillingSystem = nameof(TechMessageDto.System); var dtos = await InsertRange(); - var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System); + var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == autoDrillingSystem); //act var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken()); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(filteredDtos.Count(), response.Content); + Assert.NotNull(response.Content); + var categories = response.Content + .FirstOrDefault()?.Categories + .FirstOrDefault(e => e.Key == 0).Value; + Assert.Equal(filteredDtos.Count(), categories); } - public async Task> InsertRange() + [Fact] + public async Task GetDatesRange_returns_success() + { + //act + var response = await techMessagesClient.GetDatesRangeAsync(new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + //Assert.Equal(DateTimeOffset.MinValue, response.Content?.From); + //Assert.Equal(DateTimeOffset.MaxValue, response.Content?.To); + } + + [Fact] + public async Task GetDatesRange_AfterSave_returns_success() { //arrange + await InsertRange(); + + //act + var response = await techMessagesClient.GetDatesRangeAsync(new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.NotNull(response.Content?.From); + Assert.NotNull(response.Content?.To); + } + + [Fact] + public async Task GetPart_returns_success() + { + //arrange + var dateBegin = DateTimeOffset.UtcNow; + var take = 2; + + //act + var response = await techMessagesClient.GetPart(dateBegin, take, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.Empty(response.Content); + } + + [Fact] + public async Task GetPart_AfterSave_returns_success() + { + //arrange + var dateBegin = DateTimeOffset.UtcNow; + var take = 1; + await InsertRange(); + + //act + var response = await techMessagesClient.GetPart(dateBegin, take, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.NotEmpty(response.Content); + } + + private async Task> InsertRange() + { + //arrange + memoryCache.Remove(SystemCacheKey); + dbContext.CleanupDbSet(); + dbContext.CleanupDbSet(); + var dtos = new List() { new TechMessageDto() @@ -181,7 +259,7 @@ namespace Persistence.IntegrationTests.Controllers Timestamp = DateTimeOffset.UtcNow, Depth = 1.11, MessageText = nameof(TechMessageDto.MessageText), - System = nameof(TechMessageDto.System), + System = nameof(TechMessageDto.System).ToLower(), UserId = Guid.NewGuid() }, new TechMessageDto() @@ -191,7 +269,7 @@ namespace Persistence.IntegrationTests.Controllers Timestamp = DateTimeOffset.UtcNow, Depth = 2.22, MessageText = nameof(TechMessageDto.MessageText), - System = nameof(TechMessageDto.System), + System = nameof(TechMessageDto.System).ToLower(), UserId = Guid.NewGuid() } }; diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs index 7f81c29..b4557aa 100644 --- a/Persistence.Repository/Repositories/SetpointRepository.cs +++ b/Persistence.Repository/Repositories/SetpointRepository.cs @@ -43,6 +43,38 @@ namespace Persistence.Repository.Repositories return dtos; } + public async Task> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token) + { + var query = GetQueryReadOnly(); + var entities = await query + .Where(e => e.Created > dateBegin) + .Take(take) + .ToArrayAsync(token); + var dtos = entities + .Select(e => e.Adapt()); + + return dtos; + } + + public async Task GetDatesRangeAsync(CancellationToken token) + { + var query = GetQueryReadOnly() + .GroupBy(e => 1) + .Select(group => new + { + Min = group.Min(e => e.Created), + Max = group.Max(e => e.Created), + }); + var values = await query.FirstOrDefaultAsync(token); + var result = new DatesRangeDto() + { + From = values?.Min ?? DateTimeOffset.MinValue, + To = values?.Max ?? DateTimeOffset.MaxValue + }; + + return result; + } + public async Task>> GetLog(IEnumerable setpointKeys, CancellationToken token) { var query = GetQueryReadOnly(); @@ -56,7 +88,7 @@ namespace Persistence.Repository.Repositories return dtos; } - public async Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token) + public async Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token) { var entity = new Setpoint() { diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index 478ba0f..fad8acf 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -10,7 +10,8 @@ namespace Persistence.Repository.Repositories { public class TechMessagesRepository : ITechMessagesRepository { - private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey"; + private static readonly string SystemCacheKey = $"{typeof(Database.Entity.DrillingSystem).FullName}CacheKey"; + private const int CacheExpirationInMinutes = 60; private readonly IMemoryCache memoryCache; private DbContext db; @@ -26,40 +27,65 @@ namespace Persistence.Repository.Repositories public async Task> GetPage(RequestDto request, CancellationToken token) { var query = GetQueryReadOnly(); + var count = await query.CountAsync(token); + + var sort = request.SortSettings != string.Empty + ? request.SortSettings + : nameof(TechMessage.Timestamp); var entities = await query .SortBy(request.SortSettings) .Skip(request.Skip) .Take(request.Take) - .ToListAsync(); + .ToArrayAsync(token); + var dto = new PaginationContainer() { Skip = request.Skip, Take = request.Take, - Count = entities.Count, + Count = count, Items = entities.Select(e => e.Adapt()) }; return dto; } - public async Task> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token) + public async Task> GetStatistics(IEnumerable autoDrillingSystem, IEnumerable categoryIds, CancellationToken token) { var query = GetQueryReadOnly(); - var count = await query - .Where(e => importantId == null || e.CategoryId == importantId) - .Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem) - .GroupBy(e => e.System.Name) - .ToDictionaryAsync(e => e.Key, v => v.Count()); + var systems = autoDrillingSystem.Select(s => s.ToLower().Trim()); + var result = await query + .Where(e => systems.Count() == 0 || systems.Contains(e.System.Name.ToLower().Trim())) + .GroupBy(e => e.System.Name, (key, group) => new + { + System = key, + Categories = group + .Where(g => categoryIds.Count() == 0 || categoryIds.Contains(g.CategoryId)) + }) + .ToArrayAsync(token); - return count; + var entities = new List(); + foreach (var e in result) + { + var categories = e.Categories + .GroupBy(g => g.CategoryId) + .ToDictionary(c => c.Key, v => v.Count()); + var entity = new MessagesStatisticDto() + { + System = e.System, + Categories = categories + }; + entities.Add(entity); + } + + return entities; } public async Task> GetSystems(CancellationToken token) { - var entities = await GetSystems(); - var systems = entities.Select(e => e.Name); + var entities = await GetDrillingSystems(token); + var result = entities.Select(e => e.Name); - return systems ?? []; + return result; } public async Task InsertRange(IEnumerable dtos, CancellationToken token) @@ -69,9 +95,9 @@ namespace Persistence.Repository.Repositories foreach (var dto in dtos) { var entity = dto.Adapt(); - var systems = await GetSystems(); - var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId - ?? await CreateSystem(dto.System); + var systems = await GetDrillingSystems(token); + var systemId = systems.FirstOrDefault(e => e.Name.ToLower().Trim() == dto.System.ToLower().Trim())?.SystemId + ?? await CreateDrillingSystem(dto.System, token); entity.SystemId = systemId; @@ -84,36 +110,67 @@ namespace Persistence.Repository.Repositories return result; } - private async Task> GetSystems() + public async Task> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token) + { + var query = GetQueryReadOnly(); + var entities = await query + .Where(e => e.Timestamp > dateBegin) + .Take(take) + .ToArrayAsync(token); + var dtos = entities + .Select(e => e.Adapt()); + + return dtos; + } + + public async Task GetDatesRangeAsync(CancellationToken token) + { + var query = GetQueryReadOnly() + .GroupBy(e => 1) + .Select(group => new + { + Min = group.Min(e => e.Timestamp), + Max = group.Max(e => e.Timestamp), + }); + var values = await query.FirstOrDefaultAsync(token); + var result = new DatesRangeDto() + { + From = values?.Min ?? DateTimeOffset.MinValue, + To = values?.Max ?? DateTimeOffset.MaxValue + }; + + return result; + } + + private async Task> GetDrillingSystems(CancellationToken token) { var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f => { - f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60); + f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationInMinutes); - var query = db.Set(); - var entities = await query.ToListAsync(); - var dtos = entities.Select(e => e.Adapt()); + var query = db.Set(); + var entities = await query.ToListAsync(token); + var dtos = entities.Select(e => e.Adapt()); return dtos; }); - return systems ?? []; + return systems!; } - private async Task CreateSystem(string name) + private async Task CreateDrillingSystem(string name, CancellationToken token) { memoryCache.Remove(SystemCacheKey); - var systemId = Guid.NewGuid(); - var entity = new ADSystem() + var entity = new Database.Entity.DrillingSystem() { - SystemId = systemId, - Name = name + SystemId = default, + Name = name.ToLower().Trim() }; - await db.Set().AddAsync(entity); - await db.SaveChangesAsync(); + await db.Set().AddAsync(entity); + await db.SaveChangesAsync(token); - return systemId; + return entity.SystemId; } } } diff --git a/Persistence/API/ISetpointApi.cs b/Persistence/API/ISetpointApi.cs index 7af0895..d05fe12 100644 --- a/Persistence/API/ISetpointApi.cs +++ b/Persistence/API/ISetpointApi.cs @@ -6,7 +6,7 @@ namespace Persistence.API; /// /// Интерфейс для API, предназначенного для работы с уставками /// -public interface ISetpointApi +public interface ISetpointApi : ISyncApi { /// /// Получить актуальные значения уставок @@ -33,12 +33,12 @@ public interface ISetpointApi /// Task>>> GetLog(IEnumerable setpoitKeys, CancellationToken token); - /// - /// Метод сохранения уставки - /// - /// ключ уставки - /// значение - /// - /// - Task> Save(Guid setpointKey, object newValue, CancellationToken token); + /// + /// Метод сохранения уставки + /// + /// ключ уставки + /// значение + /// + /// + Task Save(Guid setpointKey, object newValue, Guid userId, CancellationToken token); } diff --git a/Persistence/API/ISyncApi.cs b/Persistence/API/ISyncApi.cs index 7f72812..e630ee7 100644 --- a/Persistence/API/ISyncApi.cs +++ b/Persistence/API/ISyncApi.cs @@ -6,7 +6,7 @@ namespace Persistence.API; /// /// Интерфейс для API, предназначенного для синхронизации данных /// -public interface ISyncApi where TDto : class, new() +public interface ISyncApi { /// /// Получить порцию записей, начиная с заданной даты diff --git a/Persistence/Models/ADSystemDto.cs b/Persistence/Models/DrillingSystemDto.cs similarity index 92% rename from Persistence/Models/ADSystemDto.cs rename to Persistence/Models/DrillingSystemDto.cs index d11dfc7..c2e7abc 100644 --- a/Persistence/Models/ADSystemDto.cs +++ b/Persistence/Models/DrillingSystemDto.cs @@ -3,7 +3,7 @@ /// /// Модель системы автобурения /// -public class ADSystemDto +public class DrillingSystemDto { /// /// Ключ diff --git a/Persistence/Models/MessagesStatisticDto.cs b/Persistence/Models/MessagesStatisticDto.cs new file mode 100644 index 0000000..08f0edd --- /dev/null +++ b/Persistence/Models/MessagesStatisticDto.cs @@ -0,0 +1,17 @@ +namespace Persistence.Models; + +/// +/// Статистика сообщений по системам бурения +/// +public class MessagesStatisticDto +{ + /// + /// Система бурения + /// + public required string System { get; set; } + + /// + /// Количество сообщений в соответствии с категориями важности + /// + public required Dictionary Categories { get; set; } +} diff --git a/Persistence/Models/SetpointLogDto.cs b/Persistence/Models/SetpointLogDto.cs index 484be7a..4aa61b5 100644 --- a/Persistence/Models/SetpointLogDto.cs +++ b/Persistence/Models/SetpointLogDto.cs @@ -13,5 +13,5 @@ public class SetpointLogDto : SetpointValueDto /// /// Ключ пользователя /// - public int IdUser { get; set; } + public Guid IdUser { get; set; } } diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs index db6838c..1fe41de 100644 --- a/Persistence/Repositories/ISetpointRepository.cs +++ b/Persistence/Repositories/ISetpointRepository.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Mvc; using Persistence.Models; namespace Persistence.Repositories; @@ -32,15 +33,31 @@ public interface ISetpointRepository /// Task>> GetLog(IEnumerable setpointKeys, CancellationToken token); - /// - /// Метод сохранения уставки - /// - /// ключ операции - /// ключ пользователя - /// значение - /// - /// - /// to do - /// id User учесть в соответствующем методе репозитория - Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token); + /// + /// Получить порцию записей, начиная с заданной даты + /// + /// + /// + /// + /// + Task> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token); + + /// + /// Получить диапазон дат, для которых есть данные в репозитории + /// + /// + /// + Task GetDatesRangeAsync(CancellationToken token); + + /// + /// Метод сохранения уставки + /// + /// ключ операции + /// ключ пользователя + /// значение + /// + /// + /// to do + /// id User учесть в соответствующем методе репозитория + Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token); } diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs index 74b2bf5..853f234 100644 --- a/Persistence/Repositories/ITechMessagesRepository.cs +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -34,10 +34,26 @@ namespace Persistence.Repositories /// /// Получение количества сообщений по категориям и системам автобурения /// - /// Id Категории важности + /// Id Категории важности /// Система автобурения /// /// - Task> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token); + Task> GetStatistics(IEnumerable autoDrillingSystem, IEnumerable categoryIds, CancellationToken token); + + /// + /// Получить порцию записей, начиная с заданной даты + /// + /// + /// + /// + /// + Task> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token); + + /// + /// Получить диапазон дат, для которых есть данные в репозитории + /// + /// + /// + Task GetDatesRangeAsync(CancellationToken token); } }