Внести доработки в 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;
/// <summary>
/// <EFBFBD>אבמעא ס גנולוםם<D79D>לט האםם<D79D>לט
/// Работа с временными данными
/// </summary>
[ApiController]
[Authorize]

View File

@ -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);
}
/// <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>
/// <param name="setpointKey"></param>
/// <param name="newValue"></param>
/// <param name="idUser"></param>
/// <param name="token"></param>
/// <returns></returns>
[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, 0, token);
await setpointRepository.Save(setpointKey, newValue, idUser, token);
return Ok();
}

View File

@ -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<int, string> categories = new Dictionary<int, string>()
{
{ 0, "System" },
{ 1, "Авария" },
{ 2, "Предупреждение" },
{ 3, "Инфо" },
{ 4, "Прочее" }
};
public TechMessagesController(ITechMessagesRepository techMessagesRepository)
{
@ -37,14 +46,14 @@ public class TechMessagesController : ControllerBase
/// <summary>
/// Получить статистику по системам
/// </summary>
/// <param name="importantId"></param>
/// <param name="autoDrillingSystem"></param>
/// <param name="categoryIds"></param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("statistics/{autoDrillingSystem}")]
public async Task<ActionResult<int>> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token)
[HttpGet("statistics")]
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);
}
@ -55,13 +64,41 @@ public class TechMessagesController : ControllerBase
/// <param name="token"></param>
/// <returns></returns>
[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);
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>
@ -69,9 +106,16 @@ public class TechMessagesController : ControllerBase
/// <param name="token"></param>
/// <returns></returns>
[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 result = await techMessagesRepository.InsertRange(dtos, token);
var userId = User.GetUserId<Guid>();
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<Dictionary<int, string>> GetImportantCategories()
{
var result = new Dictionary<int, string>()
{
{ 0, "System" },
{ 1, "Авария" },
{ 2, "Предупреждение" },
{ 3, "Инфо" },
{ 4, "Прочее" }
};
return Ok(result);
return Ok(categories);
}
}
}

View File

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

View File

@ -19,6 +19,12 @@ public interface ISetpointClient
[Get($"{BaseRoute}/log")]
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}/")]
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;
namespace Persistence.Client.Clients
@ -20,7 +19,13 @@ namespace Persistence.Client.Clients
[Get($"{BaseRoute}/systems")]
Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token);
[Get($"{BaseRoute}/statistics/" + "{autoDrillingSystem}")]
Task<IApiResponse<int>> GetStatistics(string? autoDrillingSystem, int? importantId, CancellationToken token);
[Get($"{BaseRoute}/range")]
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)
{
var nameIdetifier = Guid.NewGuid().ToString();
var claims = new List<Claim>()
{
new(ClaimTypes.NameIdentifier, nameIdetifier),
new("client_id", authUser.ClientId),
new("username", authUser.Username),
new("password", authUser.Password),

View File

@ -18,7 +18,7 @@ namespace Persistence.Database.Postgres.Migrations
Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"),
Created = table.Column<DateTimeOffset>(type: "timestamp with time zone", 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 =>
{

View File

@ -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
{
/// <inheritdoc />
@ -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<Guid>("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<int>("IdUser")
.HasColumnType("integer")
b.Property<Guid>("IdUser")
.HasColumnType("uuid")
.HasComment("Id автора последнего изменения");
b.Property<object>("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)

View File

@ -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<Guid>(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");
}
}
}

View File

@ -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<Guid>("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<int>("IdUser")
.HasColumnType("integer")
b.Property<Guid>("IdUser")
.HasColumnType("uuid")
.HasComment("Id автора последнего изменения");
b.Property<object>("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)

View File

@ -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; }

View File

@ -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; }
}
}

View File

@ -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; }

View File

@ -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<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]
public async Task Save_returns_success()
{

View File

@ -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<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
var requestDto = new RequestDto()
{
Skip = 1,
@ -104,8 +107,9 @@ namespace Persistence.IntegrationTests.Controllers
public async Task GetSystems_returns_success()
{
//arrange
dbContext.CleanupDbSet<ADSystem>();
memoryCache.Remove(SystemCacheKey);
dbContext.CleanupDbSet<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
//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<TechMessage>();
dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
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<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
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>()
{
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()
}
};

View File

@ -43,6 +43,38 @@ namespace Persistence.Repository.Repositories
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)
{
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()
{

View File

@ -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<PaginationContainer<TechMessageDto>> 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<TechMessageDto>()
{
Skip = request.Skip,
Take = request.Take,
Count = entities.Count,
Count = count,
Items = entities.Select(e => e.Adapt<TechMessageDto>())
};
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 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<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)
{
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<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
@ -69,9 +95,9 @@ namespace Persistence.Repository.Repositories
foreach (var dto in dtos)
{
var entity = dto.Adapt<TechMessage>();
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<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 =>
{
f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationInMinutes);
var query = db.Set<ADSystem>();
var entities = await query.ToListAsync();
var dtos = entities.Select(e => e.Adapt<ADSystemDto>());
var query = db.Set<Database.Entity.DrillingSystem>();
var entities = await query.ToListAsync(token);
var dtos = entities.Select(e => e.Adapt<Models.DrillingSystemDto>());
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);
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<ADSystem>().AddAsync(entity);
await db.SaveChangesAsync();
await db.Set<Database.Entity.DrillingSystem>().AddAsync(entity);
await db.SaveChangesAsync(token);
return systemId;
return entity.SystemId;
}
}
}

View File

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

View File

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

View File

@ -3,7 +3,7 @@
/// <summary>
/// Модель системы автобурения
/// </summary>
public class ADSystemDto
public class DrillingSystemDto
{
/// <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>
public int IdUser { get; set; }
public Guid IdUser { get; set; }
}

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Mvc;
using Persistence.Models;
namespace Persistence.Repositories;
@ -32,15 +33,31 @@ public interface ISetpointRepository
/// <returns></returns>
Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token);
/// <summary>
/// Метод сохранения уставки
/// </summary>
/// <param name="setpointKey">ключ операции</param>
/// <param name="idUser">ключ пользователя</param>
/// <param name="newValue">значение</param>
/// <param name="token"></param>
/// <returns></returns>
/// to do
/// id User учесть в соответствующем методе репозитория
Task Save(Guid setpointKey, object newValue, int idUser, 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>
/// <param name="setpointKey">ключ операции</param>
/// <param name="idUser">ключ пользователя</param>
/// <param name="newValue">значение</param>
/// <param name="token"></param>
/// <returns></returns>
/// to do
/// id User учесть в соответствующем методе репозитория
Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token);
}

View File

@ -34,10 +34,26 @@ namespace Persistence.Repositories
/// <summary>
/// Получение количества сообщений по категориям и системам автобурения
/// </summary>
/// <param name="importantId">Id Категории важности</param>
/// <param name="categoryId">Id Категории важности</param>
/// <param name="autoDrillingSystem">Система автобурения</param>
/// <param name="token"></param>
/// <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);
}
}