Внести доработки в Setpoint и TechMessage

This commit is contained in:
Roman Efremov 2024-12-02 15:14:00 +05:00
parent 097b422310
commit efed33e648
25 changed files with 482 additions and 120 deletions

View File

@ -6,7 +6,7 @@ using Persistence.Repository.Data;
namespace Persistence.API.Controllers; namespace Persistence.API.Controllers;
/// <summary> /// <summary>
/// <EFBFBD>אבמעא ס גנולוםם<D79D>לט האםם<D79D>לט /// Работа с временными данными
/// </summary> /// </summary>
[ApiController] [ApiController]
[Authorize] [Authorize]

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Persistence.Models; using Persistence.Models;
using Persistence.Repositories; using Persistence.Repositories;
@ -63,18 +64,47 @@ public class SetpointController : ControllerBase, ISetpointApi
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("range")]
public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token)
{
var result = await setpointRepository.GetDatesRangeAsync(token);
return Ok(result);
}
/// <summary>
/// Получить порцию записей, начиная с заданной даты
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("part")]
public async Task<ActionResult<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
{
var result = await setpointRepository.GetPart(dateBegin, take, token);
return Ok(result);
}
/// <summary> /// <summary>
/// Сохранить уставку /// Сохранить уставку
/// </summary> /// </summary>
/// <param name="setpointKey"></param> /// <param name="setpointKey"></param>
/// <param name="newValue"></param> /// <param name="newValue"></param>
/// <param name="idUser"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token) [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
{ {
// ToDo: вычитка idUser await setpointRepository.Save(setpointKey, newValue, idUser, token);
await setpointRepository.Save(setpointKey, newValue, 0, token);
return Ok(); return Ok();
} }

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Persistence.Models; using Persistence.Models;
using Persistence.Repositories; using Persistence.Repositories;
@ -14,6 +15,14 @@ namespace Persistence.API.Controllers;
public class TechMessagesController : ControllerBase public class TechMessagesController : ControllerBase
{ {
private readonly ITechMessagesRepository techMessagesRepository; private readonly ITechMessagesRepository techMessagesRepository;
private static readonly Dictionary<int, string> categories = new Dictionary<int, string>()
{
{ 0, "System" },
{ 1, "Авария" },
{ 2, "Предупреждение" },
{ 3, "Инфо" },
{ 4, "Прочее" }
};
public TechMessagesController(ITechMessagesRepository techMessagesRepository) public TechMessagesController(ITechMessagesRepository techMessagesRepository)
{ {
@ -37,14 +46,14 @@ public class TechMessagesController : ControllerBase
/// <summary> /// <summary>
/// Получить статистику по системам /// Получить статистику по системам
/// </summary> /// </summary>
/// <param name="importantId"></param>
/// <param name="autoDrillingSystem"></param> /// <param name="autoDrillingSystem"></param>
/// <param name="categoryIds"></param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("statistics/{autoDrillingSystem}")] [HttpGet("statistics")]
public async Task<ActionResult<int>> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token) public async Task<ActionResult<IEnumerable<MessagesStatisticDto>>> GetStatistics([FromQuery] IEnumerable<string> autoDrillingSystem, [FromQuery] IEnumerable<int> categoryIds, CancellationToken token)
{ {
var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token); var result = await techMessagesRepository.GetStatistics(autoDrillingSystem, categoryIds, token);
return Ok(result); return Ok(result);
} }
@ -55,13 +64,41 @@ public class TechMessagesController : ControllerBase
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("systems")] [HttpGet("systems")]
public async Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token) public async Task<ActionResult<Dictionary<string, int>>> GetSystems(CancellationToken token)
{ {
var result = await techMessagesRepository.GetSystems(token); var result = await techMessagesRepository.GetSystems(token);
return Ok(result); return Ok(result);
} }
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("range")]
public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token)
{
var result = await techMessagesRepository.GetDatesRangeAsync(token);
return Ok(result);
}
/// <summary>
/// Получить порцию записей, начиная с заданной даты
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("part")]
public async Task<ActionResult<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
{
var result = await techMessagesRepository.GetPart(dateBegin, take, token);
return Ok(result);
}
/// <summary> /// <summary>
/// Добавить новые технологические сообщения /// Добавить новые технологические сообщения
/// </summary> /// </summary>
@ -69,8 +106,15 @@ public class TechMessagesController : ControllerBase
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
public async Task<ActionResult<int>> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token) [ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
public async Task<IActionResult> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
{ {
var userId = User.GetUserId<Guid>();
foreach (var dto in dtos)
{
dto.UserId = userId;
}
var result = await techMessagesRepository.InsertRange(dtos, token); var result = await techMessagesRepository.InsertRange(dtos, token);
return CreatedAtAction(nameof(InsertRange), result); return CreatedAtAction(nameof(InsertRange), result);
@ -83,15 +127,6 @@ public class TechMessagesController : ControllerBase
[HttpGet("categories")] [HttpGet("categories")]
public ActionResult<Dictionary<int, string>> GetImportantCategories() public ActionResult<Dictionary<int, string>> GetImportantCategories()
{ {
var result = new Dictionary<int, string>() return Ok(categories);
{
{ 0, "System" },
{ 1, "Авария" },
{ 2, "Предупреждение" },
{ 3, "Инфо" },
{ 4, "Прочее" }
};
return Ok(result);
} }
} }

View File

@ -19,7 +19,7 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
} }
/// <summary> /// <summary>
/// Ïîëó÷èòü ñïèñîê îáúåêòîâ, óäîâëåòâîðÿþùèé äèàïàçîíó äàò /// Получить список объектов, удовлетворяющий диапазону дат
/// </summary> /// </summary>
/// <param name="dateBegin"></param> /// <param name="dateBegin"></param>
/// <param name="token"></param> /// <param name="token"></param>
@ -28,24 +28,24 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token) public async Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token)
{ {
var result = await this.timeSeriesDataRepository.GetGtDate(dateBegin, token); var result = await timeSeriesDataRepository.GetGtDate(dateBegin, token);
return Ok(result); return Ok(result);
} }
/// <summary> /// <summary>
/// Ïîëó÷èòü äèàïàçîí äàò, äëÿ êîòîðûõ åñòü äàííûå â ðåïîçèòîðèå /// Получить диапазон дат, для которых есть данные в репозиторие
/// </summary> /// </summary>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
[HttpGet("datesRange")] [HttpGet("datesRange")]
public async Task<IActionResult> GetDatesRange(CancellationToken token) public async Task<IActionResult> GetDatesRange(CancellationToken token)
{ {
var result = await this.timeSeriesDataRepository.GetDatesRange(token); var result = await timeSeriesDataRepository.GetDatesRange(token);
return Ok(result); return Ok(result);
} }
/// <summary> /// <summary>
/// Ïîëó÷èòü ñïèñîê îáúåêòîâ ñ ïðîðåæèâàíèåì, óäîâëåòâîðÿþùèé äèàïàçîíó äàò /// Получить список объектов с прореживанием, удовлетворяющий диапазону дат
/// </summary> /// </summary>
/// <param name="dateBegin"></param> /// <param name="dateBegin"></param>
/// <param name="intervalSec"></param> /// <param name="intervalSec"></param>
@ -55,12 +55,12 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
[HttpGet("resampled")] [HttpGet("resampled")]
public async Task<IActionResult> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default) public async Task<IActionResult> 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); return Ok(result);
} }
/// <summary> /// <summary>
/// Äîáàâèòü çàïèñè /// Добавить записи
/// </summary> /// </summary>
/// <param name="dtos"></param> /// <param name="dtos"></param>
/// <param name="token"></param> /// <param name="token"></param>
@ -68,7 +68,7 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
[HttpPost] [HttpPost]
public async Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token) public async Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
{ {
var result = await this.timeSeriesDataRepository.InsertRange(dtos, token); var result = await timeSeriesDataRepository.InsertRange(dtos, token);
return Ok(result); return Ok(result);
} }

View File

@ -19,6 +19,12 @@ public interface ISetpointClient
[Get($"{BaseRoute}/log")] [Get($"{BaseRoute}/log")]
Task<IApiResponse<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys); Task<IApiResponse<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys);
[Get($"{BaseRoute}/range")]
Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token);
[Get($"{BaseRoute}/part")]
Task<IApiResponse<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
[Post($"{BaseRoute}/")] [Post($"{BaseRoute}/")]
Task<IApiResponse> Save(Guid setpointKey, object newValue); Task<IApiResponse> Save(Guid setpointKey, object newValue);
} }

View File

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Mvc; using Persistence.Models;
using Persistence.Models;
using Refit; using Refit;
namespace Persistence.Client.Clients namespace Persistence.Client.Clients
@ -20,7 +19,13 @@ namespace Persistence.Client.Clients
[Get($"{BaseRoute}/systems")] [Get($"{BaseRoute}/systems")]
Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token); Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token);
[Get($"{BaseRoute}/statistics/" + "{autoDrillingSystem}")] [Get($"{BaseRoute}/range")]
Task<IApiResponse<int>> GetStatistics(string? autoDrillingSystem, int? importantId, CancellationToken token); Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token);
[Get($"{BaseRoute}/part")]
Task<IApiResponse<IEnumerable<TechMessageDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
[Get($"{BaseRoute}/statistics")]
Task<IApiResponse<IEnumerable<MessagesStatisticDto>>> GetStatistics([Query] string autoDrillingSystem, [Query] int categoryId, CancellationToken token);
} }
} }

View File

@ -29,8 +29,10 @@ public static class ApiTokenHelper
private static string CreateDefaultJwtToken(this AuthUser authUser) private static string CreateDefaultJwtToken(this AuthUser authUser)
{ {
var nameIdetifier = Guid.NewGuid().ToString();
var claims = new List<Claim>() var claims = new List<Claim>()
{ {
new(ClaimTypes.NameIdentifier, nameIdetifier),
new("client_id", authUser.ClientId), new("client_id", authUser.ClientId),
new("username", authUser.Username), new("username", authUser.Username),
new("password", authUser.Password), new("password", authUser.Password),

View File

@ -18,7 +18,7 @@ namespace Persistence.Database.Postgres.Migrations
Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"), Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"),
Created = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата изменения уставки"), Created = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата изменения уставки"),
Value = table.Column<object>(type: "jsonb", nullable: false, comment: "Значение уставки"), Value = table.Column<object>(type: "jsonb", nullable: false, comment: "Значение уставки"),
IdUser = table.Column<int>(type: "integer", nullable: false, comment: "Id автора последнего изменения") IdUser = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id автора последнего изменения")
}, },
constraints: table => constraints: table =>
{ {

View File

@ -12,7 +12,7 @@ using Persistence.Database.Model;
namespace Persistence.Database.Postgres.Migrations namespace Persistence.Database.Postgres.Migrations
{ {
[DbContext(typeof(PersistenceDbContext))] [DbContext(typeof(PersistenceDbContext))]
[Migration("20241128074729_TechMessageMigration")] [Migration("20241202072250_TechMessageMigration")]
partial class TechMessageMigration partial class TechMessageMigration
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -27,7 +27,7 @@ namespace Persistence.Database.Postgres.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b => modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -45,7 +45,7 @@ namespace Persistence.Database.Postgres.Migrations
b.HasKey("SystemId"); b.HasKey("SystemId");
b.ToTable("ADSystem"); b.ToTable("DrillingSystem");
}); });
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
@ -203,8 +203,8 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания уставки"); .HasComment("Дата создания уставки");
b.Property<int>("IdUser") b.Property<Guid>("IdUser")
.HasColumnType("integer") .HasColumnType("uuid")
.HasComment("Id автора последнего изменения"); .HasComment("Id автора последнего изменения");
b.Property<object>("Value") b.Property<object>("Value")
@ -219,7 +219,7 @@ namespace Persistence.Database.Postgres.Migrations
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{ {
b.HasOne("Persistence.Database.Entity.ADSystem", "System") b.HasOne("Persistence.Database.Entity.DrillingSystem", "System")
.WithMany() .WithMany()
.HasForeignKey("SystemId") .HasForeignKey("SystemId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)

View File

@ -12,7 +12,7 @@ namespace Persistence.Database.Postgres.Migrations
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "ADSystem", name: "DrillingSystem",
columns: table => new columns: table => new
{ {
SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения"), SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения"),
@ -21,7 +21,7 @@ namespace Persistence.Database.Postgres.Migrations
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_ADSystem", x => x.SystemId); table.PrimaryKey("PK_DrillingSystem", x => x.SystemId);
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
@ -40,9 +40,9 @@ namespace Persistence.Database.Postgres.Migrations
{ {
table.PrimaryKey("PK_TechMessage", x => x.EventId); table.PrimaryKey("PK_TechMessage", x => x.EventId);
table.ForeignKey( table.ForeignKey(
name: "FK_TechMessage_ADSystem_SystemId", name: "FK_TechMessage_DrillingSystem_SystemId",
column: x => x.SystemId, column: x => x.SystemId,
principalTable: "ADSystem", principalTable: "DrillingSystem",
principalColumn: "SystemId", principalColumn: "SystemId",
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade);
}); });
@ -60,7 +60,7 @@ namespace Persistence.Database.Postgres.Migrations
name: "TechMessage"); name: "TechMessage");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "ADSystem"); name: "DrillingSystem");
} }
} }
} }

View File

@ -24,7 +24,7 @@ namespace Persistence.Database.Postgres.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack"); NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b => modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b =>
{ {
b.Property<Guid>("SystemId") b.Property<Guid>("SystemId")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -42,7 +42,7 @@ namespace Persistence.Database.Postgres.Migrations
b.HasKey("SystemId"); b.HasKey("SystemId");
b.ToTable("ADSystem"); b.ToTable("DrillingSystem");
}); });
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
@ -200,8 +200,8 @@ namespace Persistence.Database.Postgres.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasComment("Дата создания уставки"); .HasComment("Дата создания уставки");
b.Property<int>("IdUser") b.Property<Guid>("IdUser")
.HasColumnType("integer") .HasColumnType("uuid")
.HasComment("Id автора последнего изменения"); .HasComment("Id автора последнего изменения");
b.Property<object>("Value") b.Property<object>("Value")
@ -216,7 +216,7 @@ namespace Persistence.Database.Postgres.Migrations
modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b => modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
{ {
b.HasOne("Persistence.Database.Entity.ADSystem", "System") b.HasOne("Persistence.Database.Entity.DrillingSystem", "System")
.WithMany() .WithMany()
.HasForeignKey("SystemId") .HasForeignKey("SystemId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)

View File

@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Persistence.Database.Entity; namespace Persistence.Database.Entity;
public class ADSystem public class DrillingSystem
{ {
[Key, Comment("Id системы автобурения")] [Key, Comment("Id системы автобурения")]
public Guid SystemId { get; set; } public Guid SystemId { get; set; }

View File

@ -16,6 +16,6 @@ namespace Persistence.Database.Model
public DateTimeOffset Created { get; set; } public DateTimeOffset Created { get; set; }
[Comment("Id автора последнего изменения")] [Comment("Id автора последнего изменения")]
public int IdUser { get; set; } public Guid IdUser { get; set; }
} }
} }

View File

@ -25,7 +25,7 @@ namespace Persistence.Database.Entity
public required Guid SystemId { get; set; } public required Guid SystemId { get; set; }
[Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")] [Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")]
public virtual required ADSystem System { get; set; } public virtual required DrillingSystem System { get; set; }
[Comment("Id пользователя за пультом бурильщика")] [Comment("Id пользователя за пультом бурильщика")]
public Guid UserId { get; set; } public Guid UserId { get; set; }

View File

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Persistence.Client; using Persistence.Client;
using Persistence.Client.Clients; using Persistence.Client.Clients;
using Persistence.Database.Model;
using Xunit; using Xunit;
namespace Persistence.IntegrationTests.Controllers namespace Persistence.IntegrationTests.Controllers
@ -131,6 +132,72 @@ namespace Persistence.IntegrationTests.Controllers
Assert.Equal(setpointKey, response.Content.FirstOrDefault().Key); Assert.Equal(setpointKey, response.Content.FirstOrDefault().Key);
} }
[Fact]
public async Task GetDatesRange_returns_success()
{
//arrange
dbContext.CleanupDbSet<Setpoint>();
//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<Setpoint>();
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] [Fact]
public async Task Save_returns_success() public async Task Save_returns_success()
{ {

View File

@ -11,7 +11,7 @@ namespace Persistence.IntegrationTests.Controllers
{ {
public class TechMessagesControllerTest : BaseIntegrationTest 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 ITechMessagesClient techMessagesClient;
private readonly IMemoryCache memoryCache; private readonly IMemoryCache memoryCache;
public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory) public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory)
@ -28,7 +28,10 @@ namespace Persistence.IntegrationTests.Controllers
public async Task GetPage_returns_success() public async Task GetPage_returns_success()
{ {
//arrange //arrange
memoryCache.Remove(SystemCacheKey);
dbContext.CleanupDbSet<TechMessage>(); dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
var requestDto = new RequestDto() var requestDto = new RequestDto()
{ {
Skip = 1, Skip = 1,
@ -104,8 +107,9 @@ namespace Persistence.IntegrationTests.Controllers
public async Task GetSystems_returns_success() public async Task GetSystems_returns_success()
{ {
//arrange //arrange
dbContext.CleanupDbSet<ADSystem>();
memoryCache.Remove(SystemCacheKey); memoryCache.Remove(SystemCacheKey);
dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
//act //act
var response = await techMessagesClient.GetSystems(new CancellationToken()); var response = await techMessagesClient.GetSystems(new CancellationToken());
@ -140,7 +144,10 @@ namespace Persistence.IntegrationTests.Controllers
public async Task GetStatistics_returns_success() public async Task GetStatistics_returns_success()
{ {
//arrange //arrange
memoryCache.Remove(SystemCacheKey);
dbContext.CleanupDbSet<TechMessage>(); dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
var imortantId = 1; var imortantId = 1;
var autoDrillingSystem = nameof(TechMessageDto.System); var autoDrillingSystem = nameof(TechMessageDto.System);
@ -149,7 +156,8 @@ namespace Persistence.IntegrationTests.Controllers
//assert //assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(0, response.Content); Assert.NotNull(response.Content);
Assert.Empty(response.Content);
} }
[Fact] [Fact]
@ -159,19 +167,89 @@ namespace Persistence.IntegrationTests.Controllers
var imortantId = 0; var imortantId = 0;
var autoDrillingSystem = nameof(TechMessageDto.System); var autoDrillingSystem = nameof(TechMessageDto.System);
var dtos = await InsertRange(); 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 //act
var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken()); var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken());
//assert //assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); 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<IEnumerable<TechMessageDto>> 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 //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<IEnumerable<TechMessageDto>> InsertRange()
{
//arrange
memoryCache.Remove(SystemCacheKey);
dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<DrillingSystem>();
var dtos = new List<TechMessageDto>() var dtos = new List<TechMessageDto>()
{ {
new TechMessageDto() new TechMessageDto()
@ -181,7 +259,7 @@ namespace Persistence.IntegrationTests.Controllers
Timestamp = DateTimeOffset.UtcNow, Timestamp = DateTimeOffset.UtcNow,
Depth = 1.11, Depth = 1.11,
MessageText = nameof(TechMessageDto.MessageText), MessageText = nameof(TechMessageDto.MessageText),
System = nameof(TechMessageDto.System), System = nameof(TechMessageDto.System).ToLower(),
UserId = Guid.NewGuid() UserId = Guid.NewGuid()
}, },
new TechMessageDto() new TechMessageDto()
@ -191,7 +269,7 @@ namespace Persistence.IntegrationTests.Controllers
Timestamp = DateTimeOffset.UtcNow, Timestamp = DateTimeOffset.UtcNow,
Depth = 2.22, Depth = 2.22,
MessageText = nameof(TechMessageDto.MessageText), MessageText = nameof(TechMessageDto.MessageText),
System = nameof(TechMessageDto.System), System = nameof(TechMessageDto.System).ToLower(),
UserId = Guid.NewGuid() UserId = Guid.NewGuid()
} }
}; };

View File

@ -43,6 +43,38 @@ namespace Persistence.Repository.Repositories
return dtos; return dtos;
} }
public async Task<IEnumerable<SetpointLogDto>> 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<SetpointLogDto>());
return dtos;
}
public async Task<DatesRangeDto> 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<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token) public async Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token)
{ {
var query = GetQueryReadOnly(); var query = GetQueryReadOnly();
@ -56,7 +88,7 @@ namespace Persistence.Repository.Repositories
return dtos; 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() var entity = new Setpoint()
{ {

View File

@ -10,7 +10,8 @@ namespace Persistence.Repository.Repositories
{ {
public class TechMessagesRepository : ITechMessagesRepository 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 readonly IMemoryCache memoryCache;
private DbContext db; private DbContext db;
@ -26,40 +27,65 @@ namespace Persistence.Repository.Repositories
public async Task<PaginationContainer<TechMessageDto>> GetPage(RequestDto request, CancellationToken token) public async Task<PaginationContainer<TechMessageDto>> GetPage(RequestDto request, CancellationToken token)
{ {
var query = GetQueryReadOnly(); var query = GetQueryReadOnly();
var count = await query.CountAsync(token);
var sort = request.SortSettings != string.Empty
? request.SortSettings
: nameof(TechMessage.Timestamp);
var entities = await query var entities = await query
.SortBy(request.SortSettings) .SortBy(request.SortSettings)
.Skip(request.Skip) .Skip(request.Skip)
.Take(request.Take) .Take(request.Take)
.ToListAsync(); .ToArrayAsync(token);
var dto = new PaginationContainer<TechMessageDto>() var dto = new PaginationContainer<TechMessageDto>()
{ {
Skip = request.Skip, Skip = request.Skip,
Take = request.Take, Take = request.Take,
Count = entities.Count, Count = count,
Items = entities.Select(e => e.Adapt<TechMessageDto>()) Items = entities.Select(e => e.Adapt<TechMessageDto>())
}; };
return dto; return dto;
} }
public async Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token) public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<string> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token)
{ {
var query = GetQueryReadOnly(); var query = GetQueryReadOnly();
var count = await query var systems = autoDrillingSystem.Select(s => s.ToLower().Trim());
.Where(e => importantId == null || e.CategoryId == importantId) var result = await query
.Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem) .Where(e => systems.Count() == 0 || systems.Contains(e.System.Name.ToLower().Trim()))
.GroupBy(e => e.System.Name) .GroupBy(e => e.System.Name, (key, group) => new
.ToDictionaryAsync(e => e.Key, v => v.Count()); {
System = key,
Categories = group
.Where(g => categoryIds.Count() == 0 || categoryIds.Contains(g.CategoryId))
})
.ToArrayAsync(token);
return count; var entities = new List<MessagesStatisticDto>();
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<IEnumerable<string>> GetSystems(CancellationToken token) public async Task<IEnumerable<string>> GetSystems(CancellationToken token)
{ {
var entities = await GetSystems(); var entities = await GetDrillingSystems(token);
var systems = entities.Select(e => e.Name); var result = entities.Select(e => e.Name);
return systems ?? []; return result;
} }
public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token) public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
@ -69,9 +95,9 @@ namespace Persistence.Repository.Repositories
foreach (var dto in dtos) foreach (var dto in dtos)
{ {
var entity = dto.Adapt<TechMessage>(); var entity = dto.Adapt<TechMessage>();
var systems = await GetSystems(); var systems = await GetDrillingSystems(token);
var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId var systemId = systems.FirstOrDefault(e => e.Name.ToLower().Trim() == dto.System.ToLower().Trim())?.SystemId
?? await CreateSystem(dto.System); ?? await CreateDrillingSystem(dto.System, token);
entity.SystemId = systemId; entity.SystemId = systemId;
@ -84,36 +110,67 @@ namespace Persistence.Repository.Repositories
return result; return result;
} }
private async Task<IEnumerable<ADSystemDto>> GetSystems() public async Task<IEnumerable<TechMessageDto>> 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<TechMessageDto>());
return dtos;
}
public async Task<DatesRangeDto> 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<IEnumerable<Models.DrillingSystemDto>> GetDrillingSystems(CancellationToken token)
{ {
var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f => var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f =>
{ {
f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60); f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationInMinutes);
var query = db.Set<ADSystem>(); var query = db.Set<Database.Entity.DrillingSystem>();
var entities = await query.ToListAsync(); var entities = await query.ToListAsync(token);
var dtos = entities.Select(e => e.Adapt<ADSystemDto>()); var dtos = entities.Select(e => e.Adapt<Models.DrillingSystemDto>());
return dtos; return dtos;
}); });
return systems ?? []; return systems!;
} }
private async Task<Guid> CreateSystem(string name) private async Task<Guid> CreateDrillingSystem(string name, CancellationToken token)
{ {
memoryCache.Remove(SystemCacheKey); memoryCache.Remove(SystemCacheKey);
var systemId = Guid.NewGuid(); var entity = new Database.Entity.DrillingSystem()
var entity = new ADSystem()
{ {
SystemId = systemId, SystemId = default,
Name = name Name = name.ToLower().Trim()
}; };
await db.Set<ADSystem>().AddAsync(entity); await db.Set<Database.Entity.DrillingSystem>().AddAsync(entity);
await db.SaveChangesAsync(); await db.SaveChangesAsync(token);
return systemId; return entity.SystemId;
} }
} }
} }

View File

@ -6,7 +6,7 @@ namespace Persistence.API;
/// <summary> /// <summary>
/// Интерфейс для API, предназначенного для работы с уставками /// Интерфейс для API, предназначенного для работы с уставками
/// </summary> /// </summary>
public interface ISetpointApi public interface ISetpointApi : ISyncApi<SetpointLogDto>
{ {
/// <summary> /// <summary>
/// Получить актуальные значения уставок /// Получить актуальные значения уставок
@ -40,5 +40,5 @@ public interface ISetpointApi
/// <param name="newValue">значение</param> /// <param name="newValue">значение</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token); Task<IActionResult> Save(Guid setpointKey, object newValue, Guid userId, CancellationToken token);
} }

View File

@ -6,7 +6,7 @@ namespace Persistence.API;
/// <summary> /// <summary>
/// Интерфейс для API, предназначенного для синхронизации данных /// Интерфейс для API, предназначенного для синхронизации данных
/// </summary> /// </summary>
public interface ISyncApi<TDto> where TDto : class, new() public interface ISyncApi<TDto>
{ {
/// <summary> /// <summary>
/// Получить порцию записей, начиная с заданной даты /// Получить порцию записей, начиная с заданной даты

View File

@ -3,7 +3,7 @@
/// <summary> /// <summary>
/// Модель системы автобурения /// Модель системы автобурения
/// </summary> /// </summary>
public class ADSystemDto public class DrillingSystemDto
{ {
/// <summary> /// <summary>
/// Ключ /// Ключ

View File

@ -0,0 +1,17 @@
namespace Persistence.Models;
/// <summary>
/// Статистика сообщений по системам бурения
/// </summary>
public class MessagesStatisticDto
{
/// <summary>
/// Система бурения
/// </summary>
public required string System { get; set; }
/// <summary>
/// Количество сообщений в соответствии с категориями важности
/// </summary>
public required Dictionary<int, int> Categories { get; set; }
}

View File

@ -13,5 +13,5 @@ public class SetpointLogDto : SetpointValueDto
/// <summary> /// <summary>
/// Ключ пользователя /// Ключ пользователя
/// </summary> /// </summary>
public int IdUser { get; set; } public Guid IdUser { get; set; }
} }

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Persistence.Models; using Persistence.Models;
namespace Persistence.Repositories; namespace Persistence.Repositories;
@ -32,6 +33,22 @@ public interface ISetpointRepository
/// <returns></returns> /// <returns></returns>
Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token); Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token);
/// <summary>
/// Получить порцию записей, начиная с заданной даты
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<SetpointLogDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token);
/// <summary> /// <summary>
/// Метод сохранения уставки /// Метод сохранения уставки
/// </summary> /// </summary>
@ -42,5 +59,5 @@ public interface ISetpointRepository
/// <returns></returns> /// <returns></returns>
/// to do /// to do
/// id User учесть в соответствующем методе репозитория /// id User учесть в соответствующем методе репозитория
Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token); Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token);
} }

View File

@ -34,10 +34,26 @@ namespace Persistence.Repositories
/// <summary> /// <summary>
/// Получение количества сообщений по категориям и системам автобурения /// Получение количества сообщений по категориям и системам автобурения
/// </summary> /// </summary>
/// <param name="importantId">Id Категории важности</param> /// <param name="categoryId">Id Категории важности</param>
/// <param name="autoDrillingSystem">Система автобурения</param> /// <param name="autoDrillingSystem">Система автобурения</param>
/// <param name="token"></param> /// <param name="token"></param>
/// <returns></returns> /// <returns></returns>
Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token); Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<string> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token);
/// <summary>
/// Получить порцию записей, начиная с заданной даты
/// </summary>
/// <param name="dateBegin"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<TechMessageDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token);
} }
} }