From 153f5894adcda5792854a26ec179aa35a5fc6eee Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Mon, 25 Nov 2024 13:49:07 +0500 Subject: [PATCH 01/12] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D1=85=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/TechMessagesController.cs | 44 ++++++++++++++++ .../PersistenceDbContext.cs | 5 +- Persistence.Database/Entity/TechMessage.cs | 30 +++++++++++ .../Model/IPersistenceDbContext.cs | 2 + .../Repositories/TechMessagesRepository.cs | 51 +++++++++++++++++++ Persistence/API/ITechMessages.cs | 35 +++++++++++++ Persistence/Models/TechMessageDto.cs | 40 +++++++++++++++ .../Repositories/ITechMessagesRepository.cs | 34 +++++++++++++ 8 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 Persistence.API/Controllers/TechMessagesController.cs create mode 100644 Persistence.Database/Entity/TechMessage.cs create mode 100644 Persistence.Repository/Repositories/TechMessagesRepository.cs create mode 100644 Persistence/API/ITechMessages.cs create mode 100644 Persistence/Models/TechMessageDto.cs create mode 100644 Persistence/Repositories/ITechMessagesRepository.cs diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs new file mode 100644 index 0000000..0eb183d --- /dev/null +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Mvc; +using Persistence.Models; +using Persistence.Repositories; + +namespace Persistence.API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class TechMessagesController : ControllerBase, ITechMessages + { + private readonly ITechMessagesRepository techMessagesRepository; + + public TechMessagesController(ITechMessagesRepository techMessagesRepository) + { + this.techMessagesRepository = techMessagesRepository; + } + + public Task>> GetPage(RequestDto request, CancellationToken token) + { + throw new NotImplementedException(); + } + + public async Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) + { + var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); + + return Ok(result); + } + + public async Task>> GetSystems(CancellationToken token) + { + var result = await techMessagesRepository.GetSystems(token); + + return Ok(result); + } + + public async Task> InsertRange(IEnumerable dtos, CancellationToken token) + { + var result = await techMessagesRepository.InsertRange(dtos, token); + + return Ok(result); + } + } +} diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs index 58d201c..75fd28a 100644 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ b/Persistence.Database.Postgres/PersistenceDbContext.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Persistence.Database.Entity; using System.Data.Common; namespace Persistence.Database.Model; @@ -8,7 +9,9 @@ public partial class PersistenceDbContext : DbContext, IPersistenceDbContext public DbSet Setpoint => Set(); - public PersistenceDbContext() + public DbSet TechMessage => Set(); + + public PersistenceDbContext() : base() { diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs new file mode 100644 index 0000000..b4135c1 --- /dev/null +++ b/Persistence.Database/Entity/TechMessage.cs @@ -0,0 +1,30 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Database.Entity +{ + public class TechMessage + { + [Key, Comment("Id события")] + public Guid EventId { get; set; } + + [Comment("Id Категории важности")] + public int ImportantId { get; set; } + + [Comment("Дата возникновения")] + public DateTimeOffset OccurrenceDate { get; set; } + + [Comment("Глубина забоя")] + public double? Depth { get; set; } + + [Column(TypeName = "varchar(512)"), Comment("Текст сообщения")] + public string? MessageText { get; set; } + + [Column(TypeName = "varchar(256)"), Comment("Система автобурения, к которой относится сообщение")] + public string? AutoDrillingSystem { get; set; } + + [Comment("Id пользователя за пультом бурильщика")] + public Guid UserId { get; set; } + } +} diff --git a/Persistence.Database/Model/IPersistenceDbContext.cs b/Persistence.Database/Model/IPersistenceDbContext.cs index 2c1aebb..759c4a2 100644 --- a/Persistence.Database/Model/IPersistenceDbContext.cs +++ b/Persistence.Database/Model/IPersistenceDbContext.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Persistence.Database.Entity; using System; using System.Collections.Generic; using System.Diagnostics.Metrics; @@ -12,6 +13,7 @@ public interface IPersistenceDbContext : IDisposable { DbSet DataSaub { get; } DbSet Setpoint { get; } + DbSet TechMessage { get; } DatabaseFacade Database { get; } Task SaveChangesAsync(CancellationToken cancellationToken); } diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs new file mode 100644 index 0000000..e7a37bc --- /dev/null +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -0,0 +1,51 @@ +using Mapster; +using Microsoft.EntityFrameworkCore; +using Persistence.Database.Entity; +using Persistence.Models; +using Persistence.Repositories; + +namespace Persistence.Repository.Repositories +{ + public class TechMessagesRepository : ITechMessagesRepository + { + private DbContext db; + public TechMessagesRepository(DbContext db) + { + this.db = db; + } + + protected virtual IQueryable GetQueryReadOnly() => db.Set(); + + public async Task GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) + { + var query = GetQueryReadOnly(); + var count = await query + .Where(e => e.ImportantId == importantId && e.AutoDrillingSystem == autoDrillingSystem) + .CountAsync(); + + return count; + } + + public async Task> GetSystems(CancellationToken token) + { + var query = GetQueryReadOnly(); + var entities = await query + .Select(e => e.AutoDrillingSystem ?? string.Empty) + .Distinct() + .ToArrayAsync(token); + var dtos = entities.Order(); + + return dtos; + } + + public async Task InsertRange(IEnumerable dtos, CancellationToken token) + { + var entities = dtos.Select(d => d.Adapt()); + + await db.Set().AddRangeAsync(entities, token); + var result = await db.SaveChangesAsync(token); + + return result; + } + } +} diff --git a/Persistence/API/ITechMessages.cs b/Persistence/API/ITechMessages.cs new file mode 100644 index 0000000..95af161 --- /dev/null +++ b/Persistence/API/ITechMessages.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Mvc; +using Persistence.Models; + +namespace Persistence.API +{ + /// + /// Интерфейс для API сообщений о состояниях работы систем автобурения (АБ) + /// + public interface ITechMessages : ITableDataApi + { + /// + /// Добавление новых сообщений + /// + /// + /// + /// + Task> InsertRange(IEnumerable dtos, CancellationToken token); + + /// + /// Получение списка систем АБ + /// + /// + /// + Task>> GetSystems(CancellationToken token); + + /// + /// Получение статистики + /// + /// Id Категории важности + /// Система АБ + /// + /// + Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); + } +} diff --git a/Persistence/Models/TechMessageDto.cs b/Persistence/Models/TechMessageDto.cs new file mode 100644 index 0000000..274a1d5 --- /dev/null +++ b/Persistence/Models/TechMessageDto.cs @@ -0,0 +1,40 @@ +namespace Persistence.Models +{ + public class TechMessageDto + { + /// + /// Id события + /// + public Guid EventId { get; set; } + + /// + /// Id Категории важности + /// + public int ImportantId { get; set; } + + /// + /// Дата возникновения + /// + public DateTimeOffset OccurrenceDate { get; set; } + + /// + /// Глубина забоя + /// + public double? Depth { get; set; } + + /// + /// Текст сообщения + /// + public string? MessageText { get; set; } + + /// + /// Система автобурения, к которой относится сообщение + /// + public string? AutoDrillingSystem { get; set; } + + /// + /// Id пользователя за пультом бурильщика + /// + public Guid UserId { get; set; } + } +} diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs new file mode 100644 index 0000000..c681184 --- /dev/null +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -0,0 +1,34 @@ +using Persistence.Models; + +namespace Persistence.Repositories +{ + /// + /// Интерфейс по работе с технологическими сообщениями + /// + public interface ITechMessagesRepository + { + /// + /// Добавление новых сообщений + /// + /// + /// + /// + Task InsertRange(IEnumerable dtos, CancellationToken token); + + /// + /// Получение списка уникальных названий систем АБ + /// + /// + /// + Task> GetSystems(CancellationToken token); + + /// + /// Получение количества сообщений по категориям и системам автобурения + /// + /// Id Категории важности + /// Система автобурения + /// + /// + Task GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); + } +} From acc9e6494a9df663deb0843a7f35394962857cdb Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Tue, 26 Nov 2024 10:23:48 +0500 Subject: [PATCH 02/12] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=8C=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=82=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D1=85=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/TechMessagesController.cs | 12 +- ...126044756_TechMessageMigration.Designer.cs | 175 ++++++++++++ .../20241126044756_TechMessageMigration.cs | 59 ++++ .../PersistenceDbContextModelSnapshot.cs | 38 ++- Persistence.Repository/DependencyInjection.cs | 1 + .../Extensions/EFExtensionsSortBy.cs | 267 ++++++++++++++++++ .../Repositories/TechMessagesRepository.cs | 19 ++ .../Repositories/ISetpointRepository.cs | 14 +- .../Repositories/ITechMessagesRepository.cs | 8 + 9 files changed, 582 insertions(+), 11 deletions(-) create mode 100644 Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs create mode 100644 Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs create mode 100644 Persistence.Repository/Extensions/EFExtensionsSortBy.cs diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index 0eb183d..16db89c 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -15,11 +15,15 @@ namespace Persistence.API.Controllers this.techMessagesRepository = techMessagesRepository; } - public Task>> GetPage(RequestDto request, CancellationToken token) + [HttpGet] + public async Task>> GetPage([FromQuery] RequestDto request, CancellationToken token) { - throw new NotImplementedException(); + var result = await techMessagesRepository.GetPage(request, token); + + return Ok(result); } + [HttpGet("statistics")] public async Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) { var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); @@ -27,6 +31,7 @@ namespace Persistence.API.Controllers return Ok(result); } + [HttpGet("systems")] public async Task>> GetSystems(CancellationToken token) { var result = await techMessagesRepository.GetSystems(token); @@ -34,7 +39,8 @@ namespace Persistence.API.Controllers return Ok(result); } - public async Task> InsertRange(IEnumerable dtos, CancellationToken token) + [HttpPost] + public async Task> InsertRange([FromBody] IEnumerable dtos, CancellationToken token) { var result = await techMessagesRepository.InsertRange(dtos, token); diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs new file mode 100644 index 0000000..5d6817e --- /dev/null +++ b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs @@ -0,0 +1,175 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Persistence.Database.Model; + +#nullable disable + +namespace Persistence.Database.Postgres.Migrations +{ + [DbContext(typeof(PersistenceDbContext))] + [Migration("20241126044756_TechMessageMigration")] + partial class TechMessageMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .UseCollation("Russian_Russia.1251") + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Id события"); + + b.Property("AutoDrillingSystem") + .HasColumnType("varchar(256)") + .HasComment("Система автобурения, к которой относится сообщение"); + + b.Property("Depth") + .HasColumnType("double precision") + .HasComment("Глубина забоя"); + + b.Property("ImportantId") + .HasColumnType("integer") + .HasComment("Id Категории важности"); + + b.Property("MessageText") + .HasColumnType("varchar(512)") + .HasComment("Текст сообщения"); + + b.Property("OccurrenceDate") + .HasColumnType("timestamp with time zone") + .HasComment("Дата возникновения"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasComment("Id пользователя за пультом бурильщика"); + + b.HasKey("EventId"); + + b.ToTable("TechMessage"); + }); + + modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => + { + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date"); + + b.Property("AxialLoad") + .HasColumnType("double precision") + .HasColumnName("axialLoad"); + + b.Property("BitDepth") + .HasColumnType("double precision") + .HasColumnName("bitDepth"); + + b.Property("BlockPosition") + .HasColumnType("double precision") + .HasColumnName("blockPosition"); + + b.Property("BlockSpeed") + .HasColumnType("double precision") + .HasColumnName("blockSpeed"); + + b.Property("Flow") + .HasColumnType("double precision") + .HasColumnName("flow"); + + b.Property("HookWeight") + .HasColumnType("double precision") + .HasColumnName("hookWeight"); + + b.Property("IdFeedRegulator") + .HasColumnType("integer") + .HasColumnName("idFeedRegulator"); + + b.Property("Mode") + .HasColumnType("integer") + .HasColumnName("mode"); + + b.Property("Mse") + .HasColumnType("double precision") + .HasColumnName("mse"); + + b.Property("MseState") + .HasColumnType("smallint") + .HasColumnName("mseState"); + + b.Property("Pressure") + .HasColumnType("double precision") + .HasColumnName("pressure"); + + b.Property("Pump0Flow") + .HasColumnType("double precision") + .HasColumnName("pump0Flow"); + + b.Property("Pump1Flow") + .HasColumnType("double precision") + .HasColumnName("pump1Flow"); + + b.Property("Pump2Flow") + .HasColumnType("double precision") + .HasColumnName("pump2Flow"); + + b.Property("RotorSpeed") + .HasColumnType("double precision") + .HasColumnName("rotorSpeed"); + + b.Property("RotorTorque") + .HasColumnType("double precision") + .HasColumnName("rotorTorque"); + + b.Property("User") + .HasColumnType("text") + .HasColumnName("user"); + + b.Property("WellDepth") + .HasColumnType("double precision") + .HasColumnName("wellDepth"); + + b.HasKey("Date"); + + b.ToTable("DataSaub"); + }); + + modelBuilder.Entity("Persistence.Database.Model.Setpoint", b => + { + b.Property("Key") + .HasColumnType("uuid") + .HasComment("Ключ"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasComment("Дата создания уставки"); + + b.Property("IdUser") + .HasColumnType("integer") + .HasComment("Id автора последнего изменения"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Значение уставки"); + + b.HasKey("Key", "Created"); + + b.ToTable("Setpoint"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs new file mode 100644 index 0000000..46a71a0 --- /dev/null +++ b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Database.Postgres.Migrations +{ + /// + public partial class TechMessageMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Created", + table: "Setpoint", + type: "timestamp with time zone", + nullable: false, + comment: "Дата создания уставки", + oldClrType: typeof(DateTimeOffset), + oldType: "timestamp with time zone", + oldComment: "Дата изменения уставки"); + + migrationBuilder.CreateTable( + name: "TechMessage", + columns: table => new + { + EventId = table.Column(type: "uuid", nullable: false, comment: "Id события"), + ImportantId = table.Column(type: "integer", nullable: false, comment: "Id Категории важности"), + OccurrenceDate = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"), + Depth = table.Column(type: "double precision", nullable: true, comment: "Глубина забоя"), + MessageText = table.Column(type: "varchar(512)", nullable: true, comment: "Текст сообщения"), + AutoDrillingSystem = table.Column(type: "varchar(256)", nullable: true, comment: "Система автобурения, к которой относится сообщение"), + UserId = table.Column(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика") + }, + constraints: table => + { + table.PrimaryKey("PK_TechMessage", x => x.EventId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TechMessage"); + + migrationBuilder.AlterColumn( + name: "Created", + table: "Setpoint", + type: "timestamp with time zone", + nullable: false, + comment: "Дата изменения уставки", + oldClrType: typeof(DateTimeOffset), + oldType: "timestamp with time zone", + oldComment: "Дата создания уставки"); + } + } +} diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs index f41f669..4446a1c 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs @@ -24,6 +24,42 @@ namespace Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Id события"); + + b.Property("AutoDrillingSystem") + .HasColumnType("varchar(256)") + .HasComment("Система автобурения, к которой относится сообщение"); + + b.Property("Depth") + .HasColumnType("double precision") + .HasComment("Глубина забоя"); + + b.Property("ImportantId") + .HasColumnType("integer") + .HasComment("Id Категории важности"); + + b.Property("MessageText") + .HasColumnType("varchar(512)") + .HasComment("Текст сообщения"); + + b.Property("OccurrenceDate") + .HasColumnType("timestamp with time zone") + .HasComment("Дата возникновения"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasComment("Id пользователя за пультом бурильщика"); + + b.HasKey("EventId"); + + b.ToTable("TechMessage"); + }); + modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => { b.Property("Date") @@ -115,7 +151,7 @@ namespace Persistence.Database.Postgres.Migrations b.Property("Created") .HasColumnType("timestamp with time zone") - .HasComment("Дата изменения уставки"); + .HasComment("Дата создания уставки"); b.Property("IdUser") .HasColumnType("integer") diff --git a/Persistence.Repository/DependencyInjection.cs b/Persistence.Repository/DependencyInjection.cs index 27063cb..a8a08cc 100644 --- a/Persistence.Repository/DependencyInjection.cs +++ b/Persistence.Repository/DependencyInjection.cs @@ -17,6 +17,7 @@ public static class DependencyInjection services.AddTransient, TimeSeriesDataRepository>(); services.AddTransient(); + services.AddTransient(); return services; } diff --git a/Persistence.Repository/Extensions/EFExtensionsSortBy.cs b/Persistence.Repository/Extensions/EFExtensionsSortBy.cs new file mode 100644 index 0000000..03b9c65 --- /dev/null +++ b/Persistence.Repository/Extensions/EFExtensionsSortBy.cs @@ -0,0 +1,267 @@ +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; + +namespace Persistence.Repository.Extensions; + +public static class EFExtensionsSortBy +{ + struct TypeAccessor + { + public LambdaExpression KeySelector { get; set; } + public MethodInfo OrderBy { get; set; } + public MethodInfo OrderByDescending { get; set; } + public MethodInfo ThenBy { get; set; } + public MethodInfo ThenByDescending { get; set; } + } + + private static ConcurrentDictionary> TypePropSelectors { get; set; } = + new(); + + private static readonly MethodInfo methodOrderBy = GetExtOrderMethod("OrderBy"); + + private static readonly MethodInfo methodOrderByDescending = GetExtOrderMethod("OrderByDescending"); + + private static readonly MethodInfo methodThenBy = GetExtOrderMethod("ThenBy"); + + private static readonly MethodInfo methodThenByDescending = GetExtOrderMethod("ThenByDescending"); + + private static MethodInfo GetExtOrderMethod(string methodName) + => typeof(Queryable) + .GetMethods() + .Where(m => m.Name == methodName && + m.IsGenericMethodDefinition && + m.GetParameters().Length == 2 && + m.GetParameters()[1].ParameterType.IsAssignableTo(typeof(LambdaExpression))) + .Single(); + + private static Dictionary MakeTypeAccessors(Type type) + { + var propContainer = new Dictionary(); + var properties = type.GetProperties(); + foreach (var propertyInfo in properties) + { + var name = propertyInfo.Name.ToLower(); + ParameterExpression arg = Expression.Parameter(type, "x"); + MemberExpression property = Expression.Property(arg, propertyInfo.Name); + var selector = Expression.Lambda(property, new ParameterExpression[] { arg }); + var typeAccessor = new TypeAccessor + { + KeySelector = selector, + OrderBy = methodOrderBy.MakeGenericMethod(type, propertyInfo.PropertyType), + OrderByDescending = methodOrderByDescending.MakeGenericMethod(type, propertyInfo.PropertyType), + ThenBy = methodThenBy.MakeGenericMethod(type, propertyInfo.PropertyType), + ThenByDescending = methodThenByDescending.MakeGenericMethod(type, propertyInfo.PropertyType), + }; + + propContainer.Add(name, typeAccessor); + } + + return propContainer; + } + + /// + /// Добавить в запрос сортировку по возрастанию или убыванию. + /// + /// + /// + /// + /// Свойство сортировки. + /// Состоит из названия свойства (в любом регистре) + /// и опционально указания направления сортировки "asc" или "desc" + /// + /// + /// var query = query("Date desc"); + /// + /// Запрос с примененной сортировкой + public static IOrderedQueryable SortBy( + this IQueryable query, + IEnumerable propertySorts) + { + if (propertySorts?.Any() != true) + return (IOrderedQueryable)query; + + var sortEnum = propertySorts.GetEnumerator(); + sortEnum.MoveNext(); + var orderedQuery = query.SortBy(sortEnum.Current); + + while (sortEnum.MoveNext()) + orderedQuery = orderedQuery.ThenSortBy(sortEnum.Current); + + return orderedQuery; + } + + /// + /// Добавить в запрос сортировку по возрастанию или убыванию. + /// Этот метод сбросит ранее наложенные сортировки. + /// + /// + /// + /// + /// Свойство сортировки. + /// Состоит из названия свойства (в любом регистре) + /// и опционально указания направления сортировки "asc" или "desc" + /// + /// + /// var query = query("Date desc"); + /// + /// Запрос с примененной сортировкой + public static IOrderedQueryable SortBy( + this IQueryable query, + string propertySort) + { + var parts = propertySort.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries); + var isDesc = parts.Length >= 2 && parts[1].ToLower().Trim() == "desc"; + var propertyName = parts[0]; + + var newQuery = query.SortBy(propertyName, isDesc); + return newQuery; + } + + /// + /// Добавить в запрос дополнительную сортировку по возрастанию или убыванию. + /// + /// + /// + /// + /// Свойство сортировки. + /// Состоит из названия свойства (в любом регистре) + /// и опционально указания направления сортировки "asc" или "desc" + /// + /// + /// var query = query("Date desc"); + /// + /// Запрос с примененной сортировкой + public static IOrderedQueryable ThenSortBy( + this IOrderedQueryable query, + string propertySort) + { + var parts = propertySort.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries); + var isDesc = parts.Length >= 2 && parts[1].ToLower().Trim() == "desc"; + var propertyName = parts[0]; + + var newQuery = query.ThenSortBy(propertyName, isDesc); + return newQuery; + } + + /// + /// Добавить в запрос сортировку по возрастанию или убыванию + /// + /// + /// + /// Название свойства (в любом регистре) + /// Сортировать по убыванию + /// Запрос с примененной сортировкой + public static IOrderedQueryable SortBy( + this IQueryable query, + string propertyName, + bool isDesc) + { + Type rootType = typeof(TSource); + var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors); + var propertyNameLower = propertyName.ToLower(); + + MethodInfo orderByDescending; + MethodInfo orderByAscending; + + LambdaExpression? lambdaExpression = null; + + if (propertyName.Contains('.')) + { + Type type = rootType; + ParameterExpression rootExpression = Expression.Parameter(rootType, "x"); + Expression expr = rootExpression; + + var propertyPath = propertyName.Split(".", StringSplitOptions.RemoveEmptyEntries); + + for (int i = 0; i < propertyPath.Length; i++) + { + PropertyInfo pi = type.GetProperty(propertyPath[i])!; + expr = Expression.Property(expr, pi); + type = pi.PropertyType; + } + + Type delegateType = typeof(Func<,>).MakeGenericType(rootType, type); + lambdaExpression = Expression.Lambda(delegateType, expr, rootExpression); + + orderByAscending = methodOrderBy.MakeGenericMethod(rootType, type); + orderByDescending = methodOrderByDescending.MakeGenericMethod(rootType, type); + } + else + { + var rootTypeAccessor = typePropSelector[propertyNameLower]; + orderByAscending = rootTypeAccessor.OrderBy; + orderByDescending = rootTypeAccessor.OrderByDescending; + lambdaExpression = rootTypeAccessor.KeySelector; + } + + var genericMethod = isDesc + ? orderByDescending + : orderByAscending; + + var newQuery = (IOrderedQueryable)genericMethod + .Invoke(genericMethod, new object[] { query, lambdaExpression })!; + return newQuery; + } + + /// + /// Добавить в запрос дополнительную сортировку по возрастанию или убыванию + /// + /// + /// + /// Название свойства (в любом регистре) + /// Сортировать по убыванию + /// Запрос с примененной сортировкой + public static IOrderedQueryable ThenSortBy( + this IOrderedQueryable query, + string propertyName, + bool isDesc) + { + Type rootType = typeof(TSource); + var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors); + var propertyNameLower = propertyName.ToLower(); + + MethodInfo orderByDescending; + MethodInfo orderByAscending; + + LambdaExpression? lambdaExpression = null; + + // TODO: Устранить дублирование кода + if (propertyName.Contains('.')) + { + Type type = rootType; + ParameterExpression rootExpression = Expression.Parameter(rootType, "x"); + Expression expr = rootExpression; + + var propertyPath = propertyName.Split(".", StringSplitOptions.RemoveEmptyEntries); + + for (int i = 0; i < propertyPath.Length; i++) + { + PropertyInfo pi = type.GetProperty(propertyPath[i])!; + expr = Expression.Property(expr, pi); + type = pi.PropertyType; + } + + Type delegateType = typeof(Func<,>).MakeGenericType(rootType, type); + lambdaExpression = Expression.Lambda(delegateType, expr, rootExpression); + + orderByAscending = methodThenBy.MakeGenericMethod(rootType, type); + orderByDescending = methodThenByDescending.MakeGenericMethod(rootType, type); + } + else + { + var rootTypeAccessor = typePropSelector[propertyNameLower]; + orderByAscending = rootTypeAccessor.ThenBy; + orderByDescending = rootTypeAccessor.ThenByDescending; + lambdaExpression = rootTypeAccessor.KeySelector; + } + + var genericMethod = isDesc + ? orderByDescending + : orderByAscending; + + var newQuery = (IOrderedQueryable)genericMethod + .Invoke(genericMethod, new object[] { query, lambdaExpression })!; + return newQuery; + } +} \ No newline at end of file diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index e7a37bc..f5ddb35 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -16,6 +16,25 @@ namespace Persistence.Repository.Repositories protected virtual IQueryable GetQueryReadOnly() => db.Set(); + public async Task> GetPage(RequestDto request, CancellationToken token) + { + var query = GetQueryReadOnly(); + var entities = await query + .SortBy(request.SortSettings) + .Skip(request.Skip) + .Take(request.Take) + .ToListAsync(); + var dto = new PaginationContainer() + { + Skip = request.Skip, + Take = request.Take, + Count = entities.Count, + Items = entities.Select(e => e.Adapt()) + }; + + return dto; + } + public async Task GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) { var query = GetQueryReadOnly(); diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs index 1d82b16..db6838c 100644 --- a/Persistence/Repositories/ISetpointRepository.cs +++ b/Persistence/Repositories/ISetpointRepository.cs @@ -7,13 +7,13 @@ namespace Persistence.Repositories; /// public interface ISetpointRepository { - /// - /// Получить значения уставок по набору ключей - /// - /// - /// - /// - Task> GetCurrent(IEnumerable setpointKeys, CancellationToken token); + /// + /// Получить значения уставок по набору ключей + /// + /// + /// + /// + Task> GetCurrent(IEnumerable setpointKeys, CancellationToken token); /// /// Получить значения уставок за определенный момент времени diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs index c681184..1be90e9 100644 --- a/Persistence/Repositories/ITechMessagesRepository.cs +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -7,6 +7,14 @@ namespace Persistence.Repositories /// public interface ITechMessagesRepository { + /// + /// Получить страницу списка объектов + /// + /// + /// + /// + Task> GetPage(RequestDto request, CancellationToken token); + /// /// Добавление новых сообщений /// From 7807e4aa1e956694f72dfc71541d6ed258f6d896 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Tue, 26 Nov 2024 12:27:52 +0500 Subject: [PATCH 03/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=82=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D1=85=20=D1=81=D0=BE=D0=BE=D0=B1?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/TechMessagesController.cs | 6 +- Persistence.Client/Clients/ISetpointClient.cs | 2 +- .../Clients/ITechMessagesClient.cs | 25 +++ .../Clients/ITimeSeriesClient.cs | 3 +- .../Controllers/TechMessagesControllerTest.cs | 178 ++++++++++++++++++ .../Extensions/EFCoreExtensions.cs | 14 ++ Persistence.Repository/Data/SetpointDto.cs | 28 --- .../Repositories/TechMessagesRepository.cs | 1 + Persistence/Models/TechMessageDto.cs | 3 + 9 files changed, 227 insertions(+), 33 deletions(-) create mode 100644 Persistence.Client/Clients/ITechMessagesClient.cs create mode 100644 Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs create mode 100644 Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs delete mode 100644 Persistence.Repository/Data/SetpointDto.cs diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index 16db89c..47d79d5 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -1,10 +1,12 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using Persistence.Models; using Persistence.Repositories; namespace Persistence.API.Controllers { [ApiController] + [Authorize] [Route("api/[controller]")] public class TechMessagesController : ControllerBase, ITechMessages { @@ -44,7 +46,7 @@ namespace Persistence.API.Controllers { var result = await techMessagesRepository.InsertRange(dtos, token); - return Ok(result); + return CreatedAtAction(nameof(InsertRange), result); } } } diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs index 49733f0..72d76e1 100644 --- a/Persistence.Client/Clients/ISetpointClient.cs +++ b/Persistence.Client/Clients/ISetpointClient.cs @@ -4,7 +4,7 @@ using Refit; namespace Persistence.Client.Clients; /// -/// Интерфейс для тестирования API, предназначенного для работы с уставками +/// Интерфейс клиента для работы с уставками /// public interface ISetpointClient { diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs new file mode 100644 index 0000000..1426b54 --- /dev/null +++ b/Persistence.Client/Clients/ITechMessagesClient.cs @@ -0,0 +1,25 @@ +using Persistence.Models; +using Refit; + +namespace Persistence.Client.Clients +{ + /// + /// Интерфейс клиента для хранения технологических сообщений + /// + public interface ITechMessagesClient + { + private const string BaseRoute = "/api/techMessages"; + + [Get($"{BaseRoute}")] + Task>> GetPage([Query] RequestDto request, CancellationToken token); + + [Post($"{BaseRoute}")] + Task> InsertRange([Body] IEnumerable dtos, CancellationToken token); + + [Get($"{BaseRoute}/systems")] + Task>> GetSystems(CancellationToken token); + + [Get($"{BaseRoute}/statistics")] + Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); + } +} diff --git a/Persistence.Client/Clients/ITimeSeriesClient.cs b/Persistence.Client/Clients/ITimeSeriesClient.cs index 8f7ef0e..349337b 100644 --- a/Persistence.Client/Clients/ITimeSeriesClient.cs +++ b/Persistence.Client/Clients/ITimeSeriesClient.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Mvc; -using Persistence.Models; +using Persistence.Models; using Refit; namespace Persistence.Client.Clients; diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs new file mode 100644 index 0000000..9eb0e9d --- /dev/null +++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs @@ -0,0 +1,178 @@ +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using Persistence.Client; +using Persistence.Client.Clients; +using Persistence.Database.Entity; +using Persistence.Models; +using Xunit; + +namespace Persistence.IntegrationTests.Controllers +{ + public class TechMessagesControllerTest : BaseIntegrationTest + { + private readonly ITechMessagesClient techMessagesClient; + public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory) + { + var scope = factory.Services.CreateScope(); + var persistenceClientFactory = scope.ServiceProvider + .GetRequiredService(); + + techMessagesClient = persistenceClientFactory.GetClient(); + } + + [Fact] + public async Task GetPage_returns_success() + { + //arrange + dbContext.CleanupDbSet(); + var requestDto = new RequestDto() + { + Skip = 1, + Take = 2, + SortSettings = nameof(TechMessageDto.ImportantId) + }; + + //act + var response = await techMessagesClient.GetPage(requestDto, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.Empty(response.Content.Items); + Assert.Equal(requestDto.Skip, response.Content.Skip); + Assert.Equal(requestDto.Take, response.Content.Take); + } + + [Fact] + public async Task GetPage_AfterSave_returns_success() + { + //arrange + var dtos = await InsertRange(); + var dtosCount = dtos.Count(); + var requestDto = new RequestDto() + { + Skip = 0, + Take = 2, + SortSettings = nameof(TechMessageDto.ImportantId) + }; + + //act + var response = await techMessagesClient.GetPage(requestDto, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.Equal(dtosCount, response.Content.Count); + } + + [Fact] + public async Task InsertRange_returns_success() + { + await InsertRange(); + } + + [Fact] + public async Task GetSystems_returns_success() + { + //act + dbContext.CleanupDbSet(); + var response = await techMessagesClient.GetSystems(new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + Assert.Empty(response.Content); + } + + [Fact] + public async Task GetSystems_AfterSave_returns_success() + { + //arrange + var dtos = await InsertRange(); + var systems = dtos + .Select(e => e.AutoDrillingSystem) + .Distinct() + .ToArray(); + + //act + var response = await techMessagesClient.GetSystems(new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(response.Content); + string?[]? content = response.Content?.ToArray(); + Assert.Equal(systems, content); + } + + [Fact] + public async Task GetStatistics_returns_success() + { + //arrange + dbContext.CleanupDbSet(); + var imortantId = 1; + var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem); + + //act + var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(0, response.Content); + } + + [Fact] + public async Task GetStatistics_AfterSave_returns_success() + { + //arrange + var imortantId = 1; + var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem); + var dtos = await InsertRange(); + var filteredDtos = dtos.Where(e => e.ImportantId == imortantId && e.AutoDrillingSystem == e.AutoDrillingSystem); + + //act + var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(filteredDtos.Count(), response.Content); + } + + public async Task> InsertRange() + { + //arrange + var dtos = new List() + { + new TechMessageDto() + { + EventId = Guid.NewGuid(), + ImportantId = 1, + OccurrenceDate = DateTimeOffset.UtcNow, + Depth = 1.11, + MessageText = nameof(TechMessageDto.MessageText), + AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem), + UserId = Guid.NewGuid() + }, + new TechMessageDto() + { + EventId = Guid.NewGuid(), + ImportantId = 2, + OccurrenceDate = DateTimeOffset.UtcNow, + Depth = 2.22, + MessageText = nameof(TechMessageDto.MessageText), + AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem), + UserId = Guid.NewGuid() + } + }; + + + //act + var response = await techMessagesClient.InsertRange(dtos, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal(dtos.Count, response.Content); + + return dtos; + } + } +} diff --git a/Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs b/Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs new file mode 100644 index 0000000..6b09587 --- /dev/null +++ b/Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs @@ -0,0 +1,14 @@ +using Persistence.Database.Model; + +namespace Persistence.IntegrationTests.Extensions; + +public static class EFCoreExtensions +{ + public static void CleanupDbSet(this PersistenceDbContext dbContext) + where T : class + { + var dbset = dbContext.Set(); + dbset.RemoveRange(dbset); + dbContext.SaveChanges(); + } +} diff --git a/Persistence.Repository/Data/SetpointDto.cs b/Persistence.Repository/Data/SetpointDto.cs deleted file mode 100644 index 4a20aa4..0000000 --- a/Persistence.Repository/Data/SetpointDto.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Persistence.Repository.Data -{ - /// - /// Модель для работы с уставкой - /// - public class SetpointDto - { - /// - /// Идентификатор уставки - /// - public int Id { get; set; } - - /// - /// Значение уставки - /// - public required object Value { get; set; } - - /// - /// Дата сохранения уставки - /// - public DateTimeOffset Edit { get; set; } - - /// - /// Ключ пользователя - /// - public int IdUser { get; set; } - } -} diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index f5ddb35..9abda86 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Persistence.Database.Entity; using Persistence.Models; using Persistence.Repositories; +using Persistence.Repository.Extensions; namespace Persistence.Repository.Repositories { diff --git a/Persistence/Models/TechMessageDto.cs b/Persistence/Models/TechMessageDto.cs index 274a1d5..625b22f 100644 --- a/Persistence/Models/TechMessageDto.cs +++ b/Persistence/Models/TechMessageDto.cs @@ -1,5 +1,8 @@ namespace Persistence.Models { + /// + /// Модель технологического сообщения + /// public class TechMessageDto { /// From 81474ea297cf252ee3c6bd7ff1132eaded62db42 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Tue, 26 Nov 2024 14:07:36 +0500 Subject: [PATCH 04/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/TechMessagesController.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index 47d79d5..676ae8e 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -48,5 +48,20 @@ namespace Persistence.API.Controllers return CreatedAtAction(nameof(InsertRange), result); } + + [HttpGet("categories")] + public ActionResult> GetImportantCategories() + { + var result = new Dictionary() + { + { 0, "System" }, + { 1, "Авария" }, + { 2, "Предупреждение" }, + { 3, "Инфо" }, + { 4, "Прочее" } + }; + + return Ok(result); + } } } From 0234f2096d53e730518d07a92dbee6db7b1b2dec Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Wed, 27 Nov 2024 10:45:31 +0500 Subject: [PATCH 05/12] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D0=B0=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20Keycloak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Persistence.API/Persistence.API.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Persistence.API/Persistence.API.csproj b/Persistence.API/Persistence.API.csproj index 2b8cb73..c14b509 100644 --- a/Persistence.API/Persistence.API.csproj +++ b/Persistence.API/Persistence.API.csproj @@ -8,7 +8,8 @@ - + + From dc66522c0ff4c8f7c073ed0b2a07bbe8d5773341 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Wed, 27 Nov 2024 13:08:06 +0500 Subject: [PATCH 06/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20DocumentationFile,=20=D0=BE=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/DataSaubController.cs | 4 + .../Controllers/SetpointController.cs | 100 ++++++++----- .../Controllers/TechMessagesController.cs | 140 +++++++++++------- .../Controllers/TimeSeriesController.cs | 40 ++++- Persistence.API/DependencyInjection.cs | 10 +- Persistence.API/Persistence.API.csproj | 2 + 6 files changed, 193 insertions(+), 103 deletions(-) diff --git a/Persistence.API/Controllers/DataSaubController.cs b/Persistence.API/Controllers/DataSaubController.cs index 63069a9..437aee3 100644 --- a/Persistence.API/Controllers/DataSaubController.cs +++ b/Persistence.API/Controllers/DataSaubController.cs @@ -4,6 +4,10 @@ using Persistence.Repositories; using Persistence.Repository.Data; namespace Persistence.API.Controllers; + +/// +/// +/// [ApiController] [Authorize] [Route("api/[controller]")] diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs index 9a6bd61..5a5cb52 100644 --- a/Persistence.API/Controllers/SetpointController.cs +++ b/Persistence.API/Controllers/SetpointController.cs @@ -3,51 +3,79 @@ using Microsoft.AspNetCore.Mvc; using Persistence.Models; using Persistence.Repositories; -namespace Persistence.API.Controllers +namespace Persistence.API.Controllers; + +/// +/// Работа с уставками +/// +[ApiController] +[Authorize] +[Route("api/[controller]")] +public class SetpointController : ControllerBase, ISetpointApi { - [ApiController] - [Authorize] - [Route("api/[controller]")] - public class SetpointController : ControllerBase, ISetpointApi + private readonly ISetpointRepository setpointRepository; + + public SetpointController(ISetpointRepository setpointRepository) { - private readonly ISetpointRepository setpointRepository; + this.setpointRepository = setpointRepository; + } - public SetpointController(ISetpointRepository setpointRepository) - { - this.setpointRepository = setpointRepository; - } + /// + /// Получить актуальные значения уставок + /// + /// + /// + /// + [HttpGet("current")] + public async Task>> GetCurrent([FromQuery] IEnumerable setpointKeys, CancellationToken token) + { + var result = await setpointRepository.GetCurrent(setpointKeys, token); - [HttpGet("current")] - public async Task>> GetCurrent([FromQuery] IEnumerable setpointKeys, CancellationToken token) - { - var result = await setpointRepository.GetCurrent(setpointKeys, token); + return Ok(result); + } - return Ok(result); - } + /// + /// Получить значения уставок за определенный момент времени + /// + /// + /// + /// + /// + [HttpGet("history")] + public async Task>> GetHistory([FromQuery] IEnumerable setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token) + { + var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token); - [HttpGet("history")] - public async Task>> GetHistory([FromQuery] IEnumerable setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token) - { - var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token); + return Ok(result); + } - return Ok(result); - } + /// + /// Получить историю изменений значений уставок + /// + /// + /// + /// + [HttpGet("log")] + public async Task>>> GetLog([FromQuery] IEnumerable setpointKeys, CancellationToken token) + { + var result = await setpointRepository.GetLog(setpointKeys, token); - [HttpGet("log")] - public async Task>>> GetLog([FromQuery] IEnumerable setpointKeys, CancellationToken token) - { - var result = await setpointRepository.GetLog(setpointKeys, token); + return Ok(result); + } - return Ok(result); - } + /// + /// Сохранить уставку + /// + /// + /// + /// + /// + [HttpPost] + public async Task> Save(Guid setpointKey, object newValue, CancellationToken token) + { + // ToDo: вычитка idUser + await setpointRepository.Save(setpointKey, newValue, 0, token); - [HttpPost] - public async Task> Save(Guid setpointKey, object newValue, CancellationToken token) - { - // ToDo: вычитка idUser - await setpointRepository.Save(setpointKey, newValue, 0, token); - - return Ok(); - } + return Ok(); } } diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index 676ae8e..7411c19 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -3,65 +3,95 @@ using Microsoft.AspNetCore.Mvc; using Persistence.Models; using Persistence.Repositories; -namespace Persistence.API.Controllers +namespace Persistence.API.Controllers; + +/// +/// Работа с состояниями систем автобурения (АБ) +/// +[ApiController] +[Authorize] +[Route("api/[controller]")] +public class TechMessagesController : ControllerBase, ITechMessages { - [ApiController] - [Authorize] - [Route("api/[controller]")] - public class TechMessagesController : ControllerBase, ITechMessages + private readonly ITechMessagesRepository techMessagesRepository; + + public TechMessagesController(ITechMessagesRepository techMessagesRepository) { - private readonly ITechMessagesRepository techMessagesRepository; + this.techMessagesRepository = techMessagesRepository; + } - public TechMessagesController(ITechMessagesRepository techMessagesRepository) + /// + /// Получить список технологических сообщений в виде страницы + /// + /// + /// + /// + [HttpGet] + public async Task>> GetPage([FromQuery] RequestDto request, CancellationToken token) + { + var result = await techMessagesRepository.GetPage(request, token); + + return Ok(result); + } + + /// + /// Получить статистику по системам + /// + /// + /// + /// + /// + [HttpGet("statistics")] + public async Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) + { + var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); + + return Ok(result); + } + + /// + /// Получить список всех систем + /// + /// + /// + [HttpGet("systems")] + public async Task>> GetSystems(CancellationToken token) + { + var result = await techMessagesRepository.GetSystems(token); + + return Ok(result); + } + + /// + /// Добавить новые технологические сообщения + /// + /// + /// + /// + [HttpPost] + public async Task> InsertRange([FromBody] IEnumerable dtos, CancellationToken token) + { + var result = await techMessagesRepository.InsertRange(dtos, token); + + return CreatedAtAction(nameof(InsertRange), result); + } + + /// + /// Получить словарь категорий + /// + /// + [HttpGet("categories")] + public ActionResult> GetImportantCategories() + { + var result = new Dictionary() { - this.techMessagesRepository = techMessagesRepository; - } + { 0, "System" }, + { 1, "Авария" }, + { 2, "Предупреждение" }, + { 3, "Инфо" }, + { 4, "Прочее" } + }; - [HttpGet] - public async Task>> GetPage([FromQuery] RequestDto request, CancellationToken token) - { - var result = await techMessagesRepository.GetPage(request, token); - - return Ok(result); - } - - [HttpGet("statistics")] - public async Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) - { - var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); - - return Ok(result); - } - - [HttpGet("systems")] - public async Task>> GetSystems(CancellationToken token) - { - var result = await techMessagesRepository.GetSystems(token); - - return Ok(result); - } - - [HttpPost] - public async Task> InsertRange([FromBody] IEnumerable dtos, CancellationToken token) - { - var result = await techMessagesRepository.InsertRange(dtos, token); - - return CreatedAtAction(nameof(InsertRange), result); - } - - [HttpGet("categories")] - public ActionResult> GetImportantCategories() - { - var result = new Dictionary() - { - { 0, "System" }, - { 1, "Авария" }, - { 2, "Предупреждение" }, - { 3, "Инфо" }, - { 4, "Прочее" } - }; - - return Ok(result); - } + return Ok(result); } } diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs index a4a860a..a4f00f3 100644 --- a/Persistence.API/Controllers/TimeSeriesController.cs +++ b/Persistence.API/Controllers/TimeSeriesController.cs @@ -4,6 +4,7 @@ using Persistence.Models; using Persistence.Repositories; namespace Persistence.API.Controllers; + [ApiController] [Authorize] [Route("api/[controller]")] @@ -12,12 +13,18 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi timeSeriesDataRepository; - public TimeSeriesController(ITimeSeriesDataRepository timeSeriesDataRepository) - { - this.timeSeriesDataRepository = timeSeriesDataRepository; + public TimeSeriesController(ITimeSeriesDataRepository timeSeriesDataRepository) + { + this.timeSeriesDataRepository = timeSeriesDataRepository; } - [HttpGet] + /// + /// , + /// + /// + /// + /// + [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public async Task Get(DateTimeOffset dateBegin, CancellationToken token) { @@ -25,21 +32,40 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi + /// , + /// + /// + /// + [HttpGet("datesRange")] public async Task GetDatesRange(CancellationToken token) { var result = await this.timeSeriesDataRepository.GetDatesRange(token); return Ok(result); } - [HttpGet("resampled")] + /// + /// , + /// + /// + /// + /// + /// + /// + [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] + /// + /// + /// + /// + /// + /// + [HttpPost] public async Task InsertRange(IEnumerable dtos, CancellationToken token) { var result = await this.timeSeriesDataRepository.InsertRange(dtos, token); diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs index cdfca4c..5d7c646 100644 --- a/Persistence.API/DependencyInjection.cs +++ b/Persistence.API/DependencyInjection.cs @@ -1,3 +1,4 @@ +using System.Reflection; using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; @@ -38,11 +39,10 @@ public static class DependencyInjection c.AddKeycloackSecurity(configuration); else c.AddDefaultSecurity(configuration); - //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - //var includeControllerXmlComment = true; - //options.IncludeXmlComments(xmlPath, includeControllerXmlComment); - //options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment); + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + var includeControllerXmlComment = true; + c.IncludeXmlComments(xmlPath, includeControllerXmlComment); }); } diff --git a/Persistence.API/Persistence.API.csproj b/Persistence.API/Persistence.API.csproj index c14b509..dfff363 100644 --- a/Persistence.API/Persistence.API.csproj +++ b/Persistence.API/Persistence.API.csproj @@ -5,6 +5,8 @@ enable enable Linux + True + $(NoWarn);1591 From 1e87523ab948ce168385456cf1ad9ee6dcd6b9e0 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Thu, 28 Nov 2024 08:55:50 +0500 Subject: [PATCH 07/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/TechMessagesController.cs | 6 +- Persistence.API/DependencyInjection.cs | 9 ++ Persistence.API/Startup.cs | 3 + .../20241126044756_TechMessageMigration.cs | 59 ------------- ...27123045_TechMessageMigration.Designer.cs} | 68 +++++++++++++-- .../20241127123045_TechMessageMigration.cs | 83 +++++++++++++++++++ .../PersistenceDbContextModelSnapshot.cs | 66 ++++++++++++++- Persistence.Database/Entity/ADSystem.cs | 16 ++++ Persistence.Database/Entity/TechMessage.cs | 9 +- .../Controllers/TechMessagesControllerTest.cs | 24 +++--- .../Repositories/TechMessagesRepository.cs | 71 +++++++++++++--- Persistence/API/ITechMessages.cs | 35 -------- Persistence/Models/ADSystemDto.cs | 22 +++++ Persistence/Models/TechMessageDto.cs | 19 +++-- .../Repositories/ITechMessagesRepository.cs | 7 +- 15 files changed, 355 insertions(+), 142 deletions(-) delete mode 100644 Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs rename Persistence.Database.Postgres/Migrations/{20241126044756_TechMessageMigration.Designer.cs => 20241127123045_TechMessageMigration.Designer.cs} (71%) create mode 100644 Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs create mode 100644 Persistence.Database/Entity/ADSystem.cs delete mode 100644 Persistence/API/ITechMessages.cs create mode 100644 Persistence/Models/ADSystemDto.cs diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index 7411c19..fb3315e 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -11,7 +11,7 @@ namespace Persistence.API.Controllers; [ApiController] [Authorize] [Route("api/[controller]")] -public class TechMessagesController : ControllerBase, ITechMessages +public class TechMessagesController : ControllerBase { private readonly ITechMessagesRepository techMessagesRepository; @@ -41,8 +41,8 @@ public class TechMessagesController : ControllerBase, ITechMessages /// /// /// - [HttpGet("statistics")] - public async Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) + [HttpGet("statistics/{autoDrillingSystem}")] + public async Task> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token) { var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs index 5d7c646..19cedc9 100644 --- a/Persistence.API/DependencyInjection.cs +++ b/Persistence.API/DependencyInjection.cs @@ -1,9 +1,12 @@ using System.Reflection; using System.Text.Json.Nodes; +using Mapster; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Persistence.Database.Entity; +using Persistence.Models; using Persistence.Models.Configurations; using Swashbuckle.AspNetCore.SwaggerGen; @@ -11,6 +14,12 @@ namespace Persistence.API; public static class DependencyInjection { + public static void MapsterSetup() + { + TypeAdapterConfig.GlobalSettings.Default.Config + .ForType() + .Ignore(dest => dest.System, dest => dest.SystemId); + } public static void AddSwagger(this IServiceCollection services, IConfiguration configuration) { services.AddSwaggerGen(c => diff --git a/Persistence.API/Startup.cs b/Persistence.API/Startup.cs index e074845..98ad4aa 100644 --- a/Persistence.API/Startup.cs +++ b/Persistence.API/Startup.cs @@ -23,6 +23,9 @@ public class Startup services.AddInfrastructure(); services.AddPersistenceDbContext(Configuration); services.AddJWTAuthentication(Configuration); + services.AddMemoryCache(); + + DependencyInjection.MapsterSetup(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs deleted file mode 100644 index 46a71a0..0000000 --- a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Persistence.Database.Postgres.Migrations -{ - /// - public partial class TechMessageMigration : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Created", - table: "Setpoint", - type: "timestamp with time zone", - nullable: false, - comment: "Дата создания уставки", - oldClrType: typeof(DateTimeOffset), - oldType: "timestamp with time zone", - oldComment: "Дата изменения уставки"); - - migrationBuilder.CreateTable( - name: "TechMessage", - columns: table => new - { - EventId = table.Column(type: "uuid", nullable: false, comment: "Id события"), - ImportantId = table.Column(type: "integer", nullable: false, comment: "Id Категории важности"), - OccurrenceDate = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"), - Depth = table.Column(type: "double precision", nullable: true, comment: "Глубина забоя"), - MessageText = table.Column(type: "varchar(512)", nullable: true, comment: "Текст сообщения"), - AutoDrillingSystem = table.Column(type: "varchar(256)", nullable: true, comment: "Система автобурения, к которой относится сообщение"), - UserId = table.Column(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика") - }, - constraints: table => - { - table.PrimaryKey("PK_TechMessage", x => x.EventId); - }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TechMessage"); - - migrationBuilder.AlterColumn( - name: "Created", - table: "Setpoint", - type: "timestamp with time zone", - nullable: false, - comment: "Дата изменения уставки", - oldClrType: typeof(DateTimeOffset), - oldType: "timestamp with time zone", - oldComment: "Дата создания уставки"); - } - } -} diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs similarity index 71% rename from Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs rename to Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs index 5d6817e..2e5a30a 100644 --- a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs +++ b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs @@ -12,7 +12,7 @@ using Persistence.Database.Model; namespace Persistence.Database.Postgres.Migrations { [DbContext(typeof(PersistenceDbContext))] - [Migration("20241126044756_TechMessageMigration")] + [Migration("20241127123045_TechMessageMigration")] partial class TechMessageMigration { /// @@ -27,6 +27,27 @@ namespace Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b => + { + b.Property("SystemId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Id системы автобурения"); + + b.Property("Description") + .HasColumnType("text") + .HasComment("Описание системы автобурения"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(256)") + .HasComment("Наименование системы автобурения"); + + b.HasKey("SystemId"); + + b.ToTable("ADSystem"); + }); + modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => { b.Property("EventId") @@ -34,10 +55,6 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("uuid") .HasComment("Id события"); - b.Property("AutoDrillingSystem") - .HasColumnType("varchar(256)") - .HasComment("Система автобурения, к которой относится сообщение"); - b.Property("Depth") .HasColumnType("double precision") .HasComment("Глубина забоя"); @@ -47,6 +64,7 @@ namespace Persistence.Database.Postgres.Migrations .HasComment("Id Категории важности"); b.Property("MessageText") + .IsRequired() .HasColumnType("varchar(512)") .HasComment("Текст сообщения"); @@ -54,15 +72,44 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("timestamp with time zone") .HasComment("Дата возникновения"); + b.Property("SystemId") + .HasColumnType("uuid") + .HasComment("Id системы автобурения, к которой относится сообщение"); + b.Property("UserId") .HasColumnType("uuid") .HasComment("Id пользователя за пультом бурильщика"); b.HasKey("EventId"); + b.HasIndex("SystemId"); + b.ToTable("TechMessage"); }); + modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b => + { + b.Property("IdDiscriminator") + .HasColumnType("uuid") + .HasComment("Дискриминатор ссылка на тип сохраняемых данных"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Отметка времени, строго в UTC"); + + b.Property("Set") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Набор сохраняемых данных"); + + b.HasKey("IdDiscriminator", "Timestamp"); + + b.ToTable("TimestampedSets", t => + { + t.HasComment("Общая таблица данных временных рядов"); + }); + }); + modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => { b.Property("Date") @@ -169,6 +216,17 @@ namespace Persistence.Database.Postgres.Migrations b.ToTable("Setpoint"); }); + + modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => + { + b.HasOne("Persistence.Database.Entity.ADSystem", "System") + .WithMany() + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("System"); + }); #pragma warning restore 612, 618 } } diff --git a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs new file mode 100644 index 0000000..d3a5ac2 --- /dev/null +++ b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs @@ -0,0 +1,83 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Database.Postgres.Migrations +{ + /// + public partial class TechMessageMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ADSystem", + columns: table => new + { + SystemId = table.Column(type: "uuid", nullable: false, comment: "Id системы автобурения"), + Name = table.Column(type: "varchar(256)", nullable: false, comment: "Наименование системы автобурения"), + Description = table.Column(type: "text", nullable: true, comment: "Описание системы автобурения") + }, + constraints: table => + { + table.PrimaryKey("PK_ADSystem", x => x.SystemId); + }); + + migrationBuilder.CreateTable( + name: "TimestampedSets", + columns: table => new + { + IdDiscriminator = table.Column(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"), + Timestamp = table.Column(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"), + Set = table.Column(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных") + }, + constraints: table => + { + table.PrimaryKey("PK_TimestampedSets", x => new { x.IdDiscriminator, x.Timestamp }); + }, + comment: "Общая таблица данных временных рядов"); + + migrationBuilder.CreateTable( + name: "TechMessage", + columns: table => new + { + EventId = table.Column(type: "uuid", nullable: false, comment: "Id события"), + ImportantId = table.Column(type: "integer", nullable: false, comment: "Id Категории важности"), + OccurrenceDate = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"), + Depth = table.Column(type: "double precision", nullable: true, comment: "Глубина забоя"), + MessageText = table.Column(type: "varchar(512)", nullable: false, comment: "Текст сообщения"), + SystemId = table.Column(type: "uuid", nullable: false, comment: "Id системы автобурения, к которой относится сообщение"), + UserId = table.Column(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика") + }, + constraints: table => + { + table.PrimaryKey("PK_TechMessage", x => x.EventId); + table.ForeignKey( + name: "FK_TechMessage_ADSystem_SystemId", + column: x => x.SystemId, + principalTable: "ADSystem", + principalColumn: "SystemId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_TechMessage_SystemId", + table: "TechMessage", + column: "SystemId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TechMessage"); + + migrationBuilder.DropTable( + name: "TimestampedSets"); + + migrationBuilder.DropTable( + name: "ADSystem"); + } + } +} diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs index 4446a1c..317c445 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs @@ -24,6 +24,27 @@ namespace Persistence.Database.Postgres.Migrations NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b => + { + b.Property("SystemId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasComment("Id системы автобурения"); + + b.Property("Description") + .HasColumnType("text") + .HasComment("Описание системы автобурения"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(256)") + .HasComment("Наименование системы автобурения"); + + b.HasKey("SystemId"); + + b.ToTable("ADSystem"); + }); + modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => { b.Property("EventId") @@ -31,10 +52,6 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("uuid") .HasComment("Id события"); - b.Property("AutoDrillingSystem") - .HasColumnType("varchar(256)") - .HasComment("Система автобурения, к которой относится сообщение"); - b.Property("Depth") .HasColumnType("double precision") .HasComment("Глубина забоя"); @@ -44,6 +61,7 @@ namespace Persistence.Database.Postgres.Migrations .HasComment("Id Категории важности"); b.Property("MessageText") + .IsRequired() .HasColumnType("varchar(512)") .HasComment("Текст сообщения"); @@ -51,15 +69,44 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("timestamp with time zone") .HasComment("Дата возникновения"); + b.Property("SystemId") + .HasColumnType("uuid") + .HasComment("Id системы автобурения, к которой относится сообщение"); + b.Property("UserId") .HasColumnType("uuid") .HasComment("Id пользователя за пультом бурильщика"); b.HasKey("EventId"); + b.HasIndex("SystemId"); + b.ToTable("TechMessage"); }); + modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b => + { + b.Property("IdDiscriminator") + .HasColumnType("uuid") + .HasComment("Дискриминатор ссылка на тип сохраняемых данных"); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Отметка времени, строго в UTC"); + + b.Property("Set") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Набор сохраняемых данных"); + + b.HasKey("IdDiscriminator", "Timestamp"); + + b.ToTable("TimestampedSets", t => + { + t.HasComment("Общая таблица данных временных рядов"); + }); + }); + modelBuilder.Entity("Persistence.Database.Model.DataSaub", b => { b.Property("Date") @@ -166,6 +213,17 @@ namespace Persistence.Database.Postgres.Migrations b.ToTable("Setpoint"); }); + + modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => + { + b.HasOne("Persistence.Database.Entity.ADSystem", "System") + .WithMany() + .HasForeignKey("SystemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("System"); + }); #pragma warning restore 612, 618 } } diff --git a/Persistence.Database/Entity/ADSystem.cs b/Persistence.Database/Entity/ADSystem.cs new file mode 100644 index 0000000..525134c --- /dev/null +++ b/Persistence.Database/Entity/ADSystem.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Persistence.Database.Entity; +public class ADSystem +{ + [Key, Comment("Id системы автобурения")] + public Guid SystemId { get; set; } + + [Required, Column(TypeName = "varchar(256)"), Comment("Наименование системы автобурения")] + public required string Name { get; set; } + + [Comment("Описание системы автобурения")] + public string? Description { get; set; } +} diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs index b4135c1..dce897c 100644 --- a/Persistence.Database/Entity/TechMessage.cs +++ b/Persistence.Database/Entity/TechMessage.cs @@ -19,10 +19,13 @@ namespace Persistence.Database.Entity public double? Depth { get; set; } [Column(TypeName = "varchar(512)"), Comment("Текст сообщения")] - public string? MessageText { get; set; } + public required string MessageText { get; set; } - [Column(TypeName = "varchar(256)"), Comment("Система автобурения, к которой относится сообщение")] - public string? AutoDrillingSystem { get; set; } + [Required, Comment("Id системы автобурения, к которой относится сообщение")] + public required Guid SystemId { get; set; } + + [Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")] + public virtual required ADSystem System { get; set; } [Comment("Id пользователя за пультом бурильщика")] public Guid UserId { get; set; } diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs index 9eb0e9d..73414c4 100644 --- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs @@ -29,7 +29,7 @@ namespace Persistence.IntegrationTests.Controllers { Skip = 1, Take = 2, - SortSettings = nameof(TechMessageDto.ImportantId) + SortSettings = nameof(TechMessageDto.CategoryId) }; //act @@ -53,7 +53,7 @@ namespace Persistence.IntegrationTests.Controllers { Skip = 0, Take = 2, - SortSettings = nameof(TechMessageDto.ImportantId) + SortSettings = nameof(TechMessageDto.CategoryId) }; //act @@ -90,7 +90,7 @@ namespace Persistence.IntegrationTests.Controllers //arrange var dtos = await InsertRange(); var systems = dtos - .Select(e => e.AutoDrillingSystem) + .Select(e => e.System) .Distinct() .ToArray(); @@ -110,7 +110,7 @@ namespace Persistence.IntegrationTests.Controllers //arrange dbContext.CleanupDbSet(); var imortantId = 1; - var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem); + var autoDrillingSystem = nameof(TechMessageDto.System); //act var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken()); @@ -125,9 +125,9 @@ namespace Persistence.IntegrationTests.Controllers { //arrange var imortantId = 1; - var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem); + var autoDrillingSystem = nameof(TechMessageDto.System); var dtos = await InsertRange(); - var filteredDtos = dtos.Where(e => e.ImportantId == imortantId && e.AutoDrillingSystem == e.AutoDrillingSystem); + var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System); //act var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken()); @@ -145,21 +145,21 @@ namespace Persistence.IntegrationTests.Controllers new TechMessageDto() { EventId = Guid.NewGuid(), - ImportantId = 1, - OccurrenceDate = DateTimeOffset.UtcNow, + CategoryId = 1, + Timestamp = DateTimeOffset.UtcNow, Depth = 1.11, MessageText = nameof(TechMessageDto.MessageText), - AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem), + System = nameof(TechMessageDto.System), UserId = Guid.NewGuid() }, new TechMessageDto() { EventId = Guid.NewGuid(), - ImportantId = 2, - OccurrenceDate = DateTimeOffset.UtcNow, + CategoryId = 2, + Timestamp = DateTimeOffset.UtcNow, Depth = 2.22, MessageText = nameof(TechMessageDto.MessageText), - AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem), + System = nameof(TechMessageDto.System), UserId = Guid.NewGuid() } }; diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index 9abda86..427346b 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -1,5 +1,7 @@ using Mapster; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Memory; +using Newtonsoft.Json.Linq; using Persistence.Database.Entity; using Persistence.Models; using Persistence.Repositories; @@ -9,9 +11,13 @@ namespace Persistence.Repository.Repositories { public class TechMessagesRepository : ITechMessagesRepository { + private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey"; + private readonly IMemoryCache memoryCache; private DbContext db; - public TechMessagesRepository(DbContext db) + + public TechMessagesRepository(DbContext db, IMemoryCache memoryCache) { + this.memoryCache = memoryCache; this.db = db; } @@ -36,36 +42,75 @@ namespace Persistence.Repository.Repositories return dto; } - public async Task GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token) + public async Task> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token) { var query = GetQueryReadOnly(); var count = await query - .Where(e => e.ImportantId == importantId && e.AutoDrillingSystem == autoDrillingSystem) - .CountAsync(); + .Where(e => importantId == null || e.ImportantId == importantId) + .Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem) + .GroupBy(e => e.System.Name) + .ToDictionaryAsync(e => e.Key, v => v.Count()); return count; } - public async Task> GetSystems(CancellationToken token) + public async Task> GetSystems(CancellationToken token) { - var query = GetQueryReadOnly(); - var entities = await query - .Select(e => e.AutoDrillingSystem ?? string.Empty) - .Distinct() - .ToArrayAsync(token); - var dtos = entities.Order(); + var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f => + { + f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60); - return dtos; + var query = db.Set(); + var entities = await query.ToListAsync(); + var dtos = entities.Select(e => e.Adapt()); + + return dtos; + }); + + return systems ?? []; } public async Task InsertRange(IEnumerable dtos, CancellationToken token) { - var entities = dtos.Select(d => d.Adapt()); + var entities = dtos.Select(dto => + { + var task = Task.Run(async () => + { + var entity = dto.Adapt(); + var systems = await GetSystems(token); + var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId + ?? await CreateSystem(dto.System); + + entity.SystemId = systemId; + + return entity; + }); + task.Wait(); + + return task.Result; + }); await db.Set().AddRangeAsync(entities, token); var result = await db.SaveChangesAsync(token); return result; } + + private async Task CreateSystem(string name) + { + memoryCache.Remove(SystemCacheKey); + + var systemId = Guid.NewGuid(); + var entity = new ADSystem() + { + SystemId = systemId, + Name = name + }; + + await db.Set().AddAsync(entity); + await db.SaveChangesAsync(); + + return Guid.NewGuid(); + } } } diff --git a/Persistence/API/ITechMessages.cs b/Persistence/API/ITechMessages.cs deleted file mode 100644 index 95af161..0000000 --- a/Persistence/API/ITechMessages.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Persistence.Models; - -namespace Persistence.API -{ - /// - /// Интерфейс для API сообщений о состояниях работы систем автобурения (АБ) - /// - public interface ITechMessages : ITableDataApi - { - /// - /// Добавление новых сообщений - /// - /// - /// - /// - Task> InsertRange(IEnumerable dtos, CancellationToken token); - - /// - /// Получение списка систем АБ - /// - /// - /// - Task>> GetSystems(CancellationToken token); - - /// - /// Получение статистики - /// - /// Id Категории важности - /// Система АБ - /// - /// - Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); - } -} diff --git a/Persistence/Models/ADSystemDto.cs b/Persistence/Models/ADSystemDto.cs new file mode 100644 index 0000000..d11dfc7 --- /dev/null +++ b/Persistence/Models/ADSystemDto.cs @@ -0,0 +1,22 @@ +namespace Persistence.Models; + +/// +/// Модель системы автобурения +/// +public class ADSystemDto +{ + /// + /// Ключ + /// + public Guid SystemId { get; set; } + + /// + /// Наименование + /// + public required string Name { get; set; } + + /// + /// Описание + /// + public string? Description { get; set; } +} diff --git a/Persistence/Models/TechMessageDto.cs b/Persistence/Models/TechMessageDto.cs index 625b22f..84da656 100644 --- a/Persistence/Models/TechMessageDto.cs +++ b/Persistence/Models/TechMessageDto.cs @@ -1,4 +1,6 @@ -namespace Persistence.Models +using System.ComponentModel.DataAnnotations; + +namespace Persistence.Models { /// /// Модель технологического сообщения @@ -8,32 +10,39 @@ /// /// Id события /// + [Required] public Guid EventId { get; set; } /// /// Id Категории важности /// - public int ImportantId { get; set; } + [Range(0, int.MaxValue, ErrorMessage = "Id Категории важности не может быть меньше 0")] + public int CategoryId { get; set; } /// /// Дата возникновения /// - public DateTimeOffset OccurrenceDate { get; set; } + public DateTimeOffset Timestamp { get; set; } /// /// Глубина забоя /// + [Range(0, double.MaxValue, ErrorMessage = "Глубина забоя не может быть меньше 0")] public double? Depth { get; set; } /// /// Текст сообщения /// - public string? MessageText { get; set; } + [Required] + [StringLength(512, MinimumLength = 1, ErrorMessage = "Допустимая длина текста сообщения от 1 до 512 символов")] + public required string MessageText { get; set; } /// /// Система автобурения, к которой относится сообщение /// - public string? AutoDrillingSystem { get; set; } + [Required] + [StringLength(256, MinimumLength = 1, ErrorMessage = "Допустимая длина наименования системы АБ от 1 до 256 символов")] + public required string System { get; set; } /// /// Id пользователя за пультом бурильщика diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs index 1be90e9..b8fb194 100644 --- a/Persistence/Repositories/ITechMessagesRepository.cs +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -1,4 +1,5 @@ -using Persistence.Models; +using System.Threading.Tasks; +using Persistence.Models; namespace Persistence.Repositories { @@ -28,7 +29,7 @@ namespace Persistence.Repositories /// /// /// - Task> GetSystems(CancellationToken token); + Task> GetSystems(CancellationToken token); /// /// Получение количества сообщений по категориям и системам автобурения @@ -37,6 +38,6 @@ namespace Persistence.Repositories /// Система автобурения /// /// - Task GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); + Task> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token); } } From 097b422310ae70aef9c94c59d0e38fcc4535385f Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Thu, 28 Nov 2024 13:13:07 +0500 Subject: [PATCH 08/12] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20TechMessagesRepository,=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D1=82=D0=B5=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Clients/ITechMessagesClient.cs | 7 +- ...28074729_TechMessageMigration.Designer.cs} | 18 ++--- ...=> 20241128074729_TechMessageMigration.cs} | 21 +----- .../PersistenceDbContextModelSnapshot.cs | 16 ++--- .../PersistenceDbContext.cs | 13 +++- Persistence.Database/Entity/TechMessage.cs | 4 +- .../Controllers/TechMessagesControllerTest.cs | 44 ++++++++++-- .../Repositories/TechMessagesRepository.cs | 67 ++++++++++--------- .../Repositories/ITechMessagesRepository.cs | 2 +- 9 files changed, 110 insertions(+), 82 deletions(-) rename Persistence.Database.Postgres/Migrations/{20241127123045_TechMessageMigration.Designer.cs => 20241128074729_TechMessageMigration.Designer.cs} (98%) rename Persistence.Database.Postgres/Migrations/{20241127123045_TechMessageMigration.cs => 20241128074729_TechMessageMigration.cs} (70%) diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs index 1426b54..43839fe 100644 --- a/Persistence.Client/Clients/ITechMessagesClient.cs +++ b/Persistence.Client/Clients/ITechMessagesClient.cs @@ -1,4 +1,5 @@ -using Persistence.Models; +using Microsoft.AspNetCore.Mvc; +using Persistence.Models; using Refit; namespace Persistence.Client.Clients @@ -19,7 +20,7 @@ namespace Persistence.Client.Clients [Get($"{BaseRoute}/systems")] Task>> GetSystems(CancellationToken token); - [Get($"{BaseRoute}/statistics")] - Task> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); + [Get($"{BaseRoute}/statistics/" + "{autoDrillingSystem}")] + Task> GetStatistics(string? autoDrillingSystem, int? importantId, CancellationToken token); } } diff --git a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs similarity index 98% rename from Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs rename to Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs index 2e5a30a..6678078 100644 --- a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs +++ b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs @@ -12,7 +12,7 @@ using Persistence.Database.Model; namespace Persistence.Database.Postgres.Migrations { [DbContext(typeof(PersistenceDbContext))] - [Migration("20241127123045_TechMessageMigration")] + [Migration("20241128074729_TechMessageMigration")] partial class TechMessageMigration { /// @@ -55,27 +55,27 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("uuid") .HasComment("Id события"); + b.Property("CategoryId") + .HasColumnType("integer") + .HasComment("Id Категории важности"); + b.Property("Depth") .HasColumnType("double precision") .HasComment("Глубина забоя"); - b.Property("ImportantId") - .HasColumnType("integer") - .HasComment("Id Категории важности"); - b.Property("MessageText") .IsRequired() .HasColumnType("varchar(512)") .HasComment("Текст сообщения"); - b.Property("OccurrenceDate") - .HasColumnType("timestamp with time zone") - .HasComment("Дата возникновения"); - b.Property("SystemId") .HasColumnType("uuid") .HasComment("Id системы автобурения, к которой относится сообщение"); + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Дата возникновения"); + b.Property("UserId") .HasColumnType("uuid") .HasComment("Id пользователя за пультом бурильщика"); diff --git a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs similarity index 70% rename from Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs rename to Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs index d3a5ac2..047ad52 100644 --- a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs +++ b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs @@ -24,27 +24,13 @@ namespace Persistence.Database.Postgres.Migrations table.PrimaryKey("PK_ADSystem", x => x.SystemId); }); - migrationBuilder.CreateTable( - name: "TimestampedSets", - columns: table => new - { - IdDiscriminator = table.Column(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"), - Timestamp = table.Column(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"), - Set = table.Column(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных") - }, - constraints: table => - { - table.PrimaryKey("PK_TimestampedSets", x => new { x.IdDiscriminator, x.Timestamp }); - }, - comment: "Общая таблица данных временных рядов"); - migrationBuilder.CreateTable( name: "TechMessage", columns: table => new { EventId = table.Column(type: "uuid", nullable: false, comment: "Id события"), - ImportantId = table.Column(type: "integer", nullable: false, comment: "Id Категории важности"), - OccurrenceDate = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"), + CategoryId = table.Column(type: "integer", nullable: false, comment: "Id Категории важности"), + Timestamp = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"), Depth = table.Column(type: "double precision", nullable: true, comment: "Глубина забоя"), MessageText = table.Column(type: "varchar(512)", nullable: false, comment: "Текст сообщения"), SystemId = table.Column(type: "uuid", nullable: false, comment: "Id системы автобурения, к которой относится сообщение"), @@ -73,9 +59,6 @@ namespace Persistence.Database.Postgres.Migrations migrationBuilder.DropTable( name: "TechMessage"); - migrationBuilder.DropTable( - name: "TimestampedSets"); - migrationBuilder.DropTable( name: "ADSystem"); } diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs index 317c445..f5533cc 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs @@ -52,27 +52,27 @@ namespace Persistence.Database.Postgres.Migrations .HasColumnType("uuid") .HasComment("Id события"); + b.Property("CategoryId") + .HasColumnType("integer") + .HasComment("Id Категории важности"); + b.Property("Depth") .HasColumnType("double precision") .HasComment("Глубина забоя"); - b.Property("ImportantId") - .HasColumnType("integer") - .HasComment("Id Категории важности"); - b.Property("MessageText") .IsRequired() .HasColumnType("varchar(512)") .HasComment("Текст сообщения"); - b.Property("OccurrenceDate") - .HasColumnType("timestamp with time zone") - .HasComment("Дата возникновения"); - b.Property("SystemId") .HasColumnType("uuid") .HasComment("Id системы автобурения, к которой относится сообщение"); + b.Property("Timestamp") + .HasColumnType("timestamp with time zone") + .HasComment("Дата возникновения"); + b.Property("UserId") .HasColumnType("uuid") .HasComment("Id пользователя за пультом бурильщика"); diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs index f9f9a16..89b09db 100644 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ b/Persistence.Database.Postgres/PersistenceDbContext.cs @@ -12,7 +12,7 @@ public partial class PersistenceDbContext : DbContext public DbSet TechMessage => Set(); - public DbSet TimestampedSets => Set(); + public DbSet TimestampedSets => Set(); public PersistenceDbContext() : base() @@ -42,5 +42,14 @@ public partial class PersistenceDbContext : DbContext modelBuilder.Entity() .Property(e => e.Set) .HasJsonConversion(); - } + + modelBuilder.Entity(entity => + { + entity.HasOne(t => t.System) + .WithMany() + .HasForeignKey(t => t.SystemId) + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + } } diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs index dce897c..eacc234 100644 --- a/Persistence.Database/Entity/TechMessage.cs +++ b/Persistence.Database/Entity/TechMessage.cs @@ -10,10 +10,10 @@ namespace Persistence.Database.Entity public Guid EventId { get; set; } [Comment("Id Категории важности")] - public int ImportantId { get; set; } + public int CategoryId { get; set; } [Comment("Дата возникновения")] - public DateTimeOffset OccurrenceDate { get; set; } + public DateTimeOffset Timestamp { get; set; } [Comment("Глубина забоя")] public double? Depth { get; set; } diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs index 73414c4..3902681 100644 --- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs @@ -1,4 +1,5 @@ using System.Net; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Persistence.Client; using Persistence.Client.Clients; @@ -10,7 +11,9 @@ namespace Persistence.IntegrationTests.Controllers { public class TechMessagesControllerTest : BaseIntegrationTest { + private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey"; private readonly ITechMessagesClient techMessagesClient; + private readonly IMemoryCache memoryCache; public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory) { var scope = factory.Services.CreateScope(); @@ -18,6 +21,7 @@ namespace Persistence.IntegrationTests.Controllers .GetRequiredService(); techMessagesClient = persistenceClientFactory.GetClient(); + memoryCache = scope.ServiceProvider.GetRequiredService(); } [Fact] @@ -29,7 +33,7 @@ namespace Persistence.IntegrationTests.Controllers { Skip = 1, Take = 2, - SortSettings = nameof(TechMessageDto.CategoryId) + SortSettings = nameof(TechMessage.CategoryId) }; //act @@ -53,7 +57,7 @@ namespace Persistence.IntegrationTests.Controllers { Skip = 0, Take = 2, - SortSettings = nameof(TechMessageDto.CategoryId) + SortSettings = nameof(TechMessage.CategoryId) }; //act @@ -71,11 +75,39 @@ namespace Persistence.IntegrationTests.Controllers await InsertRange(); } + [Fact] + public async Task InsertRange_returns_BadRequest() + { + //arrange + var dtos = new List() + { + new TechMessageDto() + { + EventId = Guid.NewGuid(), + CategoryId = -1, // < 0 + Timestamp = DateTimeOffset.UtcNow, + Depth = -1, // < 0 + MessageText = string.Empty, // length < 0 + System = string.Concat(Enumerable.Repeat(nameof(TechMessageDto.System), 100)), // length > 256 + UserId = Guid.NewGuid() + } + }; + + //act + var response = await techMessagesClient.InsertRange(dtos, new CancellationToken()); + + //assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + [Fact] public async Task GetSystems_returns_success() { + //arrange + dbContext.CleanupDbSet(); + memoryCache.Remove(SystemCacheKey); + //act - dbContext.CleanupDbSet(); var response = await techMessagesClient.GetSystems(new CancellationToken()); //assert @@ -113,7 +145,7 @@ namespace Persistence.IntegrationTests.Controllers var autoDrillingSystem = nameof(TechMessageDto.System); //act - var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken()); + var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken()); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -124,13 +156,13 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetStatistics_AfterSave_returns_success() { //arrange - var imortantId = 1; + var imortantId = 0; var autoDrillingSystem = nameof(TechMessageDto.System); var dtos = await InsertRange(); var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System); //act - var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken()); + var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken()); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index 427346b..478ba0f 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -1,7 +1,6 @@ using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; -using Newtonsoft.Json.Linq; using Persistence.Database.Entity; using Persistence.Models; using Persistence.Repositories; @@ -21,7 +20,8 @@ namespace Persistence.Repository.Repositories this.db = db; } - protected virtual IQueryable GetQueryReadOnly() => db.Set(); + protected virtual IQueryable GetQueryReadOnly() => db.Set() + .Include(e => e.System); public async Task> GetPage(RequestDto request, CancellationToken token) { @@ -46,7 +46,7 @@ namespace Persistence.Repository.Repositories { var query = GetQueryReadOnly(); var count = await query - .Where(e => importantId == null || e.ImportantId == importantId) + .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()); @@ -54,7 +54,37 @@ namespace Persistence.Repository.Repositories return count; } - public async Task> GetSystems(CancellationToken token) + public async Task> GetSystems(CancellationToken token) + { + var entities = await GetSystems(); + var systems = entities.Select(e => e.Name); + + return systems ?? []; + } + + public async Task InsertRange(IEnumerable dtos, CancellationToken token) + { + + var entities = new List(); + 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); + + entity.SystemId = systemId; + + entities.Add(entity); + } + + await db.Set().AddRangeAsync(entities, token); + var result = await db.SaveChangesAsync(token); + + return result; + } + + private async Task> GetSystems() { var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f => { @@ -69,33 +99,6 @@ namespace Persistence.Repository.Repositories return systems ?? []; } - - public async Task InsertRange(IEnumerable dtos, CancellationToken token) - { - var entities = dtos.Select(dto => - { - var task = Task.Run(async () => - { - var entity = dto.Adapt(); - var systems = await GetSystems(token); - var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId - ?? await CreateSystem(dto.System); - - entity.SystemId = systemId; - - return entity; - }); - task.Wait(); - - return task.Result; - }); - - await db.Set().AddRangeAsync(entities, token); - var result = await db.SaveChangesAsync(token); - - return result; - } - private async Task CreateSystem(string name) { memoryCache.Remove(SystemCacheKey); @@ -110,7 +113,7 @@ namespace Persistence.Repository.Repositories await db.Set().AddAsync(entity); await db.SaveChangesAsync(); - return Guid.NewGuid(); + return systemId; } } } diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs index b8fb194..74b2bf5 100644 --- a/Persistence/Repositories/ITechMessagesRepository.cs +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -29,7 +29,7 @@ namespace Persistence.Repositories /// /// /// - Task> GetSystems(CancellationToken token); + Task> GetSystems(CancellationToken token); /// /// Получение количества сообщений по категориям и системам автобурения From efed33e6486ec657d08eef95975078afcef1c43b Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Mon, 2 Dec 2024 15:14:00 +0500 Subject: [PATCH 09/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B2?= =?UTF-8?q?=20Setpoint=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); } } From 7092324a5461ff6402de11484159a2eb4e0139f9 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Wed, 4 Dec 2024 14:18:57 +0500 Subject: [PATCH 10/12] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20Insert=20=D0=BD?= =?UTF-8?q?=D0=B0=20Add?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Persistence.API/Controllers/SetpointController.cs | 6 +++--- Persistence.API/Controllers/TechMessagesController.cs | 6 +++--- Persistence.API/Controllers/TimeSeriesController.cs | 4 ++-- Persistence.API/Controllers/TimestampedSetController.cs | 4 ++-- Persistence.Repository/Repositories/SetpointRepository.cs | 2 +- .../Repositories/TechMessagesRepository.cs | 2 +- .../Repositories/TimeSeriesDataCachedRepository.cs | 4 ++-- .../Repositories/TimeSeriesDataRepository.cs | 2 +- .../Repositories/TimestampedSetRepository.cs | 2 +- Persistence/API/ISetpointApi.cs | 2 +- Persistence/API/ITimeSeriesDataApi.cs | 2 +- Persistence/Repositories/AbstractChangeLogRepository.cs | 2 +- Persistence/Repositories/ISetpointRepository.cs | 2 +- Persistence/Repositories/ITechMessagesRepository.cs | 2 +- Persistence/Repositories/ITimeSeriesDataRepository.cs | 2 +- Persistence/Repositories/ITimestampedSetRepository.cs | 2 +- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs index 108c3ef..1bae2c6 100644 --- a/Persistence.API/Controllers/SetpointController.cs +++ b/Persistence.API/Controllers/SetpointController.cs @@ -102,10 +102,10 @@ public class SetpointController : ControllerBase, ISetpointApi /// [HttpPost] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] - public async Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token) + public async Task Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token) { - await setpointRepository.Save(setpointKey, newValue, idUser, token); + await setpointRepository.Add(setpointKey, newValue, idUser, token); - return Ok(); + return CreatedAtAction(nameof(Add), true); } } diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index 4e91802..bb26c6a 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -107,7 +107,7 @@ public class TechMessagesController : ControllerBase /// [HttpPost] [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] - public async Task InsertRange([FromBody] IEnumerable dtos, CancellationToken token) + public async Task AddRange([FromBody] IEnumerable dtos, CancellationToken token) { var userId = User.GetUserId(); foreach (var dto in dtos) @@ -115,9 +115,9 @@ public class TechMessagesController : ControllerBase dto.UserId = userId; } - var result = await techMessagesRepository.InsertRange(dtos, token); + var result = await techMessagesRepository.AddRange(dtos, token); - return CreatedAtAction(nameof(InsertRange), result); + return CreatedAtAction(nameof(AddRange), result); } /// diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs index 90c78c8..6991759 100644 --- a/Persistence.API/Controllers/TimeSeriesController.cs +++ b/Persistence.API/Controllers/TimeSeriesController.cs @@ -66,9 +66,9 @@ public class TimeSeriesController : ControllerBase, ITimeSeriesDataApi /// [HttpPost] - public async Task InsertRange(IEnumerable dtos, CancellationToken token) + public async Task AddRange(IEnumerable dtos, CancellationToken token) { - var result = await timeSeriesDataRepository.InsertRange(dtos, token); + var result = await timeSeriesDataRepository.AddRange(dtos, token); return Ok(result); } diff --git a/Persistence.API/Controllers/TimestampedSetController.cs b/Persistence.API/Controllers/TimestampedSetController.cs index f18e4c8..bd0e97e 100644 --- a/Persistence.API/Controllers/TimestampedSetController.cs +++ b/Persistence.API/Controllers/TimestampedSetController.cs @@ -32,9 +32,9 @@ public class TimestampedSetController : ControllerBase /// кол-во затронутых записей [HttpPost] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] - public async Task InsertRange([FromRoute]Guid idDiscriminator, [FromBody]IEnumerable sets, CancellationToken token) + public async Task AddRange([FromRoute]Guid idDiscriminator, [FromBody]IEnumerable sets, CancellationToken token) { - var result = await repository.InsertRange(idDiscriminator, sets, token); + var result = await repository.AddRange(idDiscriminator, sets, token); return Ok(result); } diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs index b4557aa..8f0492c 100644 --- a/Persistence.Repository/Repositories/SetpointRepository.cs +++ b/Persistence.Repository/Repositories/SetpointRepository.cs @@ -88,7 +88,7 @@ namespace Persistence.Repository.Repositories return dtos; } - public async Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token) + public async Task Add(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 fad8acf..04bab02 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -88,7 +88,7 @@ namespace Persistence.Repository.Repositories return result; } - public async Task InsertRange(IEnumerable dtos, CancellationToken token) + public async Task AddRange(IEnumerable dtos, CancellationToken token) { var entities = new List(); diff --git a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs index 20dbe00..2037f09 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs @@ -48,9 +48,9 @@ public class TimeSeriesDataCachedRepository : TimeSeriesDataRepos return items; } - public override async Task InsertRange(IEnumerable dtos, CancellationToken token) + public override async Task AddRange(IEnumerable dtos, CancellationToken token) { - var result = await base.InsertRange(dtos, token); + var result = await base.AddRange(dtos, token); if (result > 0) { diff --git a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs index d73caf7..0e58c76 100644 --- a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs +++ b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs @@ -41,7 +41,7 @@ public class TimeSeriesDataRepository : ITimeSeriesDataRepository return dtos; } - public virtual async Task InsertRange(IEnumerable dtos, CancellationToken token) + public virtual async Task AddRange(IEnumerable dtos, CancellationToken token) { var entities = dtos.Select(d => d.Adapt()); diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs index ad9a6cf..a67b823 100644 --- a/Persistence.Repository/Repositories/TimestampedSetRepository.cs +++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs @@ -20,7 +20,7 @@ public class TimestampedSetRepository : ITimestampedSetRepository this.db = db; } - public Task InsertRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token) + public Task AddRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token) { var entities = sets.Select(set => new TimestampedSet(idDiscriminator, set.Timestamp.ToUniversalTime(), set.Set)); var dbSet = db.Set(); diff --git a/Persistence/API/ISetpointApi.cs b/Persistence/API/ISetpointApi.cs index d05fe12..0d23d69 100644 --- a/Persistence/API/ISetpointApi.cs +++ b/Persistence/API/ISetpointApi.cs @@ -40,5 +40,5 @@ public interface ISetpointApi : ISyncApi /// значение /// /// - Task Save(Guid setpointKey, object newValue, Guid userId, CancellationToken token); + Task Add(Guid setpointKey, object newValue, Guid userId, CancellationToken token); } diff --git a/Persistence/API/ITimeSeriesDataApi.cs b/Persistence/API/ITimeSeriesDataApi.cs index a2406aa..51b9332 100644 --- a/Persistence/API/ITimeSeriesDataApi.cs +++ b/Persistence/API/ITimeSeriesDataApi.cs @@ -28,7 +28,7 @@ public interface ITimeSeriesDataApi : ITimeSeriesBaseDataApi /// /// /// - Task InsertRange(IEnumerable dtos, CancellationToken token); + Task AddRange(IEnumerable dtos, CancellationToken token); } diff --git a/Persistence/Repositories/AbstractChangeLogRepository.cs b/Persistence/Repositories/AbstractChangeLogRepository.cs index 88cf511..d7b1cbc 100644 --- a/Persistence/Repositories/AbstractChangeLogRepository.cs +++ b/Persistence/Repositories/AbstractChangeLogRepository.cs @@ -74,7 +74,7 @@ namespace Persistence.Repositories; // throw new NotImplementedException(); // } -// public async Task InsertRange(int idUser, IEnumerable dtos, CancellationToken token) +// public async Task AddRange(int idUser, IEnumerable dtos, CancellationToken token) // { // using var transaction = dbContext.Database.BeginTransaction(); // try diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs index 1fe41de..502b44a 100644 --- a/Persistence/Repositories/ISetpointRepository.cs +++ b/Persistence/Repositories/ISetpointRepository.cs @@ -59,5 +59,5 @@ public interface ISetpointRepository /// /// to do /// id User учесть в соответствующем методе репозитория - Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token); + Task Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token); } diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs index 853f234..2ddf71c 100644 --- a/Persistence/Repositories/ITechMessagesRepository.cs +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -22,7 +22,7 @@ namespace Persistence.Repositories /// /// /// - Task InsertRange(IEnumerable dtos, CancellationToken token); + Task AddRange(IEnumerable dtos, CancellationToken token); /// /// Получение списка уникальных названий систем АБ diff --git a/Persistence/Repositories/ITimeSeriesDataRepository.cs b/Persistence/Repositories/ITimeSeriesDataRepository.cs index 9de7fc7..aa2c9ff 100644 --- a/Persistence/Repositories/ITimeSeriesDataRepository.cs +++ b/Persistence/Repositories/ITimeSeriesDataRepository.cs @@ -15,5 +15,5 @@ public interface ITimeSeriesDataRepository : ISyncRepository, ITimeS /// /// /// - Task InsertRange(IEnumerable dtos, CancellationToken token); + Task AddRange(IEnumerable dtos, CancellationToken token); } diff --git a/Persistence/Repositories/ITimestampedSetRepository.cs b/Persistence/Repositories/ITimestampedSetRepository.cs index 27627c3..c350739 100644 --- a/Persistence/Repositories/ITimestampedSetRepository.cs +++ b/Persistence/Repositories/ITimestampedSetRepository.cs @@ -55,5 +55,5 @@ public interface ITimestampedSetRepository /// /// /// - Task InsertRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token); + Task AddRange(Guid idDiscriminator, IEnumerable sets, CancellationToken token); } \ No newline at end of file From dd888793fd5d4a54cdd63400b5d62d3534ba4d8d Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Wed, 4 Dec 2024 14:44:33 +0500 Subject: [PATCH 11/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Persistence.Client/Clients/ISetpointClient.cs | 2 +- .../Clients/ITechMessagesClient.cs | 2 +- .../Clients/ITimeSeriesClient.cs | 2 +- .../Clients/ITimestampedSetClient.cs | 2 +- .../Controllers/SetpointControllerTest.cs | 41 +++++++++++++------ .../Controllers/TechMessagesControllerTest.cs | 4 +- .../TimeSeriesBaseControllerTest.cs | 2 +- .../TimestampedSetControllerTest.cs | 18 ++++---- .../Repositories/SetpointRepository.cs | 2 +- .../Repositories/TechMessagesRepository.cs | 2 +- 10 files changed, 46 insertions(+), 31 deletions(-) diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs index 886e034..0b81ffa 100644 --- a/Persistence.Client/Clients/ISetpointClient.cs +++ b/Persistence.Client/Clients/ISetpointClient.cs @@ -26,5 +26,5 @@ public interface ISetpointClient Task>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token); [Post($"{BaseRoute}/")] - Task Save(Guid setpointKey, object newValue); + Task Add(Guid setpointKey, object newValue); } diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs index cbdf7ef..878c6cf 100644 --- a/Persistence.Client/Clients/ITechMessagesClient.cs +++ b/Persistence.Client/Clients/ITechMessagesClient.cs @@ -14,7 +14,7 @@ namespace Persistence.Client.Clients Task>> GetPage([Query] RequestDto request, CancellationToken token); [Post($"{BaseRoute}")] - Task> InsertRange([Body] IEnumerable dtos, CancellationToken token); + Task> AddRange([Body] IEnumerable dtos, CancellationToken token); [Get($"{BaseRoute}/systems")] Task>> GetSystems(CancellationToken token); diff --git a/Persistence.Client/Clients/ITimeSeriesClient.cs b/Persistence.Client/Clients/ITimeSeriesClient.cs index 349337b..8e97836 100644 --- a/Persistence.Client/Clients/ITimeSeriesClient.cs +++ b/Persistence.Client/Clients/ITimeSeriesClient.cs @@ -8,7 +8,7 @@ public interface ITimeSeriesClient private const string BaseRoute = "/api/dataSaub"; [Post($"{BaseRoute}")] - Task> InsertRange(IEnumerable dtos); + Task> AddRange(IEnumerable dtos); [Get($"{BaseRoute}")] Task>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd); diff --git a/Persistence.Client/Clients/ITimestampedSetClient.cs b/Persistence.Client/Clients/ITimestampedSetClient.cs index 95e8bd1..bbff603 100644 --- a/Persistence.Client/Clients/ITimestampedSetClient.cs +++ b/Persistence.Client/Clients/ITimestampedSetClient.cs @@ -20,7 +20,7 @@ public interface ITimestampedSetClient /// /// [Post(baseUrl)] - Task> InsertRange(Guid idDiscriminator, IEnumerable sets); + Task> AddRange(Guid idDiscriminator, IEnumerable sets); /// /// Получение данных с фильтрацией. Значение фильтра null - отключен diff --git a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs index d314cec..2c455a0 100644 --- a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs @@ -1,4 +1,5 @@ using System.Net; +using Microsoft.AspNetCore.Mvc.TagHelpers.Cache; using Microsoft.Extensions.DependencyInjection; using Persistence.Client; using Persistence.Client.Clients; @@ -47,7 +48,7 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetCurrent_AfterSave_returns_success() { //arrange - var setpointKey = await Save(); + var setpointKey = await Add(); //act var response = await setpointClient.GetCurrent([setpointKey]); @@ -83,7 +84,7 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetHistory_AfterSave_returns_success() { //arrange - var setpointKey = await Save(); + var setpointKey = await Add(); var historyMoment = DateTimeOffset.UtcNow; historyMoment = historyMoment.AddDays(1); @@ -120,7 +121,7 @@ namespace Persistence.IntegrationTests.Controllers public async Task GetLog_AfterSave_returns_success() { //arrange - var setpointKey = await Save(); + var setpointKey = await Add(); //act var response = await setpointClient.GetLog([setpointKey]); @@ -144,8 +145,8 @@ namespace Persistence.IntegrationTests.Controllers //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); + Assert.Equal(DateTimeOffset.MinValue, response.Content!.From); + Assert.Equal(DateTimeOffset.MaxValue, response.Content!.To); } [Fact] @@ -153,7 +154,12 @@ namespace Persistence.IntegrationTests.Controllers { //arrange dbContext.CleanupDbSet(); - await Save(); + + await Add(); + + var dateBegin = DateTimeOffset.MinValue; + var take = 1; + var part = await setpointClient.GetPart(dateBegin, take, new CancellationToken()); //act var response = await setpointClient.GetDatesRangeAsync(new CancellationToken()); @@ -161,8 +167,17 @@ namespace Persistence.IntegrationTests.Controllers //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.NotNull(response.Content); - Assert.NotNull(response.Content?.From); - Assert.NotNull(response.Content?.To); + + var expectedValue = part.Content! + .FirstOrDefault()!.Created + .ToString("dd.MM.yyyy-HH:mm:ss"); + var actualValueFrom = response.Content.From + .ToString("dd.MM.yyyy-HH:mm:ss"); + Assert.Equal(expectedValue, actualValueFrom); + + var actualValueTo = response.Content.To + .ToString("dd.MM.yyyy-HH:mm:ss"); + Assert.Equal(expectedValue, actualValueTo); } [Fact] @@ -187,7 +202,7 @@ namespace Persistence.IntegrationTests.Controllers //arrange var dateBegin = DateTimeOffset.UtcNow; var take = 1; - await Save(); + await Add(); //act var response = await setpointClient.GetPart(dateBegin, take, new CancellationToken()); @@ -201,10 +216,10 @@ namespace Persistence.IntegrationTests.Controllers [Fact] public async Task Save_returns_success() { - await Save(); + await Add(); } - private async Task Save() + private async Task Add() { //arrange var setpointKey = Guid.NewGuid(); @@ -215,10 +230,10 @@ namespace Persistence.IntegrationTests.Controllers }; //act - var response = await setpointClient.Save(setpointKey, setpointValue); + var response = await setpointClient.Add(setpointKey, setpointValue); //assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); return setpointKey; } diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs index ec4e40a..1f194ad 100644 --- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs @@ -97,7 +97,7 @@ namespace Persistence.IntegrationTests.Controllers }; //act - var response = await techMessagesClient.InsertRange(dtos, new CancellationToken()); + var response = await techMessagesClient.AddRange(dtos, new CancellationToken()); //assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); @@ -276,7 +276,7 @@ namespace Persistence.IntegrationTests.Controllers //act - var response = await techMessagesClient.InsertRange(dtos, new CancellationToken()); + var response = await techMessagesClient.AddRange(dtos, new CancellationToken()); //assert Assert.Equal(HttpStatusCode.Created, response.StatusCode); diff --git a/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs index 87efa43..08bb11e 100644 --- a/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs @@ -30,7 +30,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat var expected = dto.Adapt(); //act - var response = await timeSeriesClient.InsertRange(new TDto[] { expected }); + var response = await timeSeriesClient.AddRange(new TDto[] { expected }); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs index aa33e1b..3fc7dff 100644 --- a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs @@ -25,7 +25,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest IEnumerable testSets = Generate(10, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); // act - var response = await client.InsertRange(idDiscriminator, testSets); + var response = await client.AddRange(idDiscriminator, testSets); // assert Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); @@ -39,7 +39,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest Guid idDiscriminator = Guid.NewGuid(); int count = 10; IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); // act var response = await client.Get(idDiscriminator, null, null, 0, int.MaxValue); @@ -58,7 +58,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest Guid idDiscriminator = Guid.NewGuid(); int count = 10; IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); string[] props = ["A"]; // act @@ -86,7 +86,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest var dateMin = DateTimeOffset.Now; var dateMax = DateTimeOffset.Now.AddSeconds(count); IEnumerable testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); var tail = testSets.OrderBy(t => t.Timestamp).Skip(count / 2).Take(int.MaxValue); var geDate = tail.First().Timestamp; var tolerance = TimeSpan.FromSeconds(1); @@ -111,7 +111,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest Guid idDiscriminator = Guid.NewGuid(); int count = 10; IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); var expectedCount = count / 2; // act @@ -133,7 +133,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest var expectedCount = 1; int count = 10 + expectedCount; IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); // act var response = await client.Get(idDiscriminator, null, null, count - expectedCount, count); @@ -152,7 +152,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest Guid idDiscriminator = Guid.NewGuid(); int count = 10; IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); var expectedCount = 8; // act @@ -174,7 +174,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest var dateMin = DateTimeOffset.Now; var dateMax = DateTimeOffset.Now.AddSeconds(count-1); IEnumerable testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); var tolerance = TimeSpan.FromSeconds(1); // act @@ -195,7 +195,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest Guid idDiscriminator = Guid.NewGuid(); int count = 144; IEnumerable testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7))); - var insertResponse = await client.InsertRange(idDiscriminator, testSets); + var insertResponse = await client.AddRange(idDiscriminator, testSets); // act var response = await client.Count(idDiscriminator); diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs index 8f0492c..f0e921f 100644 --- a/Persistence.Repository/Repositories/SetpointRepository.cs +++ b/Persistence.Repository/Repositories/SetpointRepository.cs @@ -47,7 +47,7 @@ namespace Persistence.Repository.Repositories { var query = GetQueryReadOnly(); var entities = await query - .Where(e => e.Created > dateBegin) + .Where(e => e.Created >= dateBegin) .Take(take) .ToArrayAsync(token); var dtos = entities diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index 04bab02..38d24e8 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -114,7 +114,7 @@ namespace Persistence.Repository.Repositories { var query = GetQueryReadOnly(); var entities = await query - .Where(e => e.Timestamp > dateBegin) + .Where(e => e.Timestamp >= dateBegin) .Take(take) .ToArrayAsync(token); var dtos = entities From 2228c841397a1532c492e7d52dcfba36938a82d4 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Wed, 4 Dec 2024 16:29:15 +0500 Subject: [PATCH 12/12] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Persistence.API/Controllers/SetpointController.cs | 7 ++++--- Persistence.API/Controllers/TechMessagesController.cs | 6 +----- .../Repositories/TechMessagesRepository.cs | 3 ++- Persistence/API/ISetpointApi.cs | 2 +- Persistence/Repositories/ITechMessagesRepository.cs | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs index 1bae2c6..42a5ff5 100644 --- a/Persistence.API/Controllers/SetpointController.cs +++ b/Persistence.API/Controllers/SetpointController.cs @@ -101,10 +101,11 @@ public class SetpointController : ControllerBase, ISetpointApi /// /// [HttpPost] - [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] - public async Task Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token) + [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)] + public async Task Add(Guid setpointKey, object newValue, CancellationToken token) { - await setpointRepository.Add(setpointKey, newValue, idUser, token); + var userId = User.GetUserId(); + await setpointRepository.Add(setpointKey, newValue, userId, token); return CreatedAtAction(nameof(Add), true); } diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs index bb26c6a..d2691c1 100644 --- a/Persistence.API/Controllers/TechMessagesController.cs +++ b/Persistence.API/Controllers/TechMessagesController.cs @@ -110,12 +110,8 @@ public class TechMessagesController : ControllerBase public async Task AddRange([FromBody] IEnumerable dtos, CancellationToken token) { var userId = User.GetUserId(); - foreach (var dto in dtos) - { - dto.UserId = userId; - } - var result = await techMessagesRepository.AddRange(dtos, token); + var result = await techMessagesRepository.AddRange(dtos, userId, token); return CreatedAtAction(nameof(AddRange), result); } diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs index 38d24e8..c838619 100644 --- a/Persistence.Repository/Repositories/TechMessagesRepository.cs +++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs @@ -88,7 +88,7 @@ namespace Persistence.Repository.Repositories return result; } - public async Task AddRange(IEnumerable dtos, CancellationToken token) + public async Task AddRange(IEnumerable dtos, Guid userId, CancellationToken token) { var entities = new List(); @@ -100,6 +100,7 @@ namespace Persistence.Repository.Repositories ?? await CreateDrillingSystem(dto.System, token); entity.SystemId = systemId; + entity.UserId = userId; entities.Add(entity); } diff --git a/Persistence/API/ISetpointApi.cs b/Persistence/API/ISetpointApi.cs index 0d23d69..b1504a2 100644 --- a/Persistence/API/ISetpointApi.cs +++ b/Persistence/API/ISetpointApi.cs @@ -40,5 +40,5 @@ public interface ISetpointApi : ISyncApi /// значение /// /// - Task Add(Guid setpointKey, object newValue, Guid userId, CancellationToken token); + Task Add(Guid setpointKey, object newValue, CancellationToken token); } diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs index 2ddf71c..92e8f70 100644 --- a/Persistence/Repositories/ITechMessagesRepository.cs +++ b/Persistence/Repositories/ITechMessagesRepository.cs @@ -22,7 +22,7 @@ namespace Persistence.Repositories /// /// /// - Task AddRange(IEnumerable dtos, CancellationToken token); + Task AddRange(IEnumerable dtos, Guid userId, CancellationToken token); /// /// Получение списка уникальных названий систем АБ