Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
f5ed62eb7d | |||
f31805a769 | |||
b79d29f08e | |||
23e2f86957 | |||
61dd5110bd | |||
afe7310f73 | |||
4fa00de88e | |||
c496cea07a | |||
3806e395eb | |||
f6648b812d | |||
1fdd199954 | |||
c751f74b35 | |||
6518aeabf1 | |||
d9cbc9022d | |||
098c180b12 | |||
b7806493a2 | |||
3a1ea55be2 | |||
7d5370bf43 | |||
9e55d6791c | |||
e5ffd42fb3 | |||
4d24eb9445 | |||
b5e255b940 | |||
5f2535b517 | |||
9e375b4831 | |||
531b14938f | |||
6fd79f6c4a | |||
90c62b9ede | |||
ef72d985be | |||
d746f85fe4 | |||
84e7ec274c |
@ -1,15 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.API;
|
||||
using Persistence.Repositories;
|
||||
using Persistence.Repository.Data;
|
||||
|
||||
namespace Persistence.API.Controllers;
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
public class DataSaubController : TimeSeriesController<DataSaubDto>
|
||||
{
|
||||
public DataSaubController(ITimeSeriesDataRepository<DataSaubDto> timeSeriesDataRepository) : base(timeSeriesDataRepository)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
53
Persistence.API/Controllers/SetpointController.cs
Normal file
53
Persistence.API/Controllers/SetpointController.cs
Normal file
@ -0,0 +1,53 @@
|
||||
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 SetpointController : ControllerBase, ISetpointApi
|
||||
{
|
||||
private readonly ISetpointRepository setpointRepository;
|
||||
|
||||
public SetpointController(ISetpointRepository setpointRepository)
|
||||
{
|
||||
this.setpointRepository = setpointRepository;
|
||||
}
|
||||
|
||||
[HttpGet("current")]
|
||||
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
|
||||
{
|
||||
var result = await setpointRepository.GetCurrent(setpointKeys, token);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("history")]
|
||||
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistory([FromQuery] IEnumerable<Guid> setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token)
|
||||
{
|
||||
var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("log")]
|
||||
public async Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
|
||||
{
|
||||
var result = await setpointRepository.GetLog(setpointKeys, token);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token)
|
||||
{
|
||||
// ToDo: вычитка idUser
|
||||
await setpointRepository.Save(setpointKey, newValue, 0, token);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
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 TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDto>
|
||||
where TDto : class, ITimeSeriesAbstractDto, new()
|
||||
@ -17,22 +19,32 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
|
||||
public async Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token)
|
||||
{
|
||||
var result = await this.timeSeriesDataRepository.GetAsync(dateBegin, dateEnd, token);
|
||||
var result = await this.timeSeriesDataRepository.GetGtDate(dateBegin, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("datesRange")]
|
||||
public Task<IActionResult> GetDatesRangeAsync(CancellationToken token)
|
||||
public async Task<IActionResult> GetDatesRange(CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var result = await this.timeSeriesDataRepository.GetDatesRange(token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[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);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> InsertRangeAsync(IEnumerable<TDto> dtos, CancellationToken token)
|
||||
public async Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
|
||||
{
|
||||
var result = await this.timeSeriesDataRepository.InsertRange(dtos, token);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
195
Persistence.API/DependencyInjection.cs
Normal file
195
Persistence.API/DependencyInjection.cs
Normal file
@ -0,0 +1,195 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Persistence.Models.Configurations;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Persistence.API;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static void AddSwagger(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.MapType<TimeSpan>(() => new OpenApiSchema { Type = "string", Example = new OpenApiString("0.00:00:00") });
|
||||
c.MapType<DateOnly>(() => new OpenApiSchema { Type = "string", Format = "date" });
|
||||
c.MapType<JsonValue>(() => new OpenApiSchema
|
||||
{
|
||||
AnyOf = new OpenApiSchema[]
|
||||
{
|
||||
new OpenApiSchema {Type = "string", Format = "string" },
|
||||
new OpenApiSchema {Type = "number", Format = "int32" },
|
||||
new OpenApiSchema {Type = "number", Format = "float" },
|
||||
}
|
||||
});
|
||||
|
||||
c.CustomOperationIds(e =>
|
||||
{
|
||||
return $"{e.ActionDescriptor.RouteValues["action"]}";
|
||||
});
|
||||
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Persistence web api", Version = "v1" });
|
||||
|
||||
var needUseKeyCloak = configuration.GetSection("NeedUseKeyCloak").Get<bool>();
|
||||
if (needUseKeyCloak)
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
#region Authentication
|
||||
public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var needUseKeyCloak = configuration
|
||||
.GetSection("NeedUseKeyCloak")
|
||||
.Get<bool>();
|
||||
if (needUseKeyCloak)
|
||||
services.AddKeyCloakAuthentication(configuration);
|
||||
else services.AddDefaultAuthentication(configuration);
|
||||
}
|
||||
|
||||
private static void AddKeyCloakAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.Audience = configuration["Authentication:Audience"];
|
||||
options.MetadataAddress = configuration["Authentication:MetadataAddress"]!;
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidIssuer = configuration["Authentication:ValidIssuer"],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddDefaultAuthentication(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = JwtParams.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = JwtParams.Audience,
|
||||
ValidateLifetime = true,
|
||||
IssuerSigningKey = JwtParams.SecurityKey,
|
||||
ValidateIssuerSigningKey = false
|
||||
};
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var accessToken = context.Request.Headers["Authorization"]
|
||||
.ToString()
|
||||
.Replace(JwtBearerDefaults.AuthenticationScheme, string.Empty)
|
||||
.Trim();
|
||||
|
||||
context.Token = accessToken;
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnTokenValidated = context =>
|
||||
{
|
||||
var username = context.Principal?.Claims
|
||||
.FirstOrDefault(e => e.Type == "username")?.Value;
|
||||
|
||||
var password = context.Principal?.Claims
|
||||
.FirstOrDefault(e => e.Type == "password")?.Value;
|
||||
|
||||
var keyCloakUser = configuration
|
||||
.GetSection(nameof(AuthUser))
|
||||
.Get<AuthUser>()!;
|
||||
|
||||
if (username != keyCloakUser.Username || password != keyCloakUser.Password)
|
||||
{
|
||||
context.Fail("username or password did not match");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Security (Swagger)
|
||||
private static void AddKeycloackSecurity(this SwaggerGenOptions options, IConfiguration configuration)
|
||||
{
|
||||
options.AddSecurityDefinition("Keycloack", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = @"JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below. Example: 'Bearer 12345abcdef'",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
Implicit = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri(configuration["Authentication:AuthorizationUrl"]),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Keycloack"
|
||||
},
|
||||
Scheme = "Bearer",
|
||||
Name = "Bearer",
|
||||
In = ParameterLocation.Header,
|
||||
},
|
||||
new List<string>()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddDefaultSecurity(this SwaggerGenOptions options, IConfiguration configuration)
|
||||
{
|
||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = @"JWT Authorization header using the Bearer scheme. Enter 'Bearer' [space] and then your token in the text input below. Example: 'Bearer 12345abcdef'",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
Scheme = "Bearer",
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
},
|
||||
Scheme = "oauth2",
|
||||
Name = "Bearer",
|
||||
In = ParameterLocation.Header,
|
||||
},
|
||||
new List<string>()
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
}
|
26
Persistence.API/Extensions.cs
Normal file
26
Persistence.API/Extensions.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.ComponentModel;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Persistence.API;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static T GetUserId<T>(this ClaimsPrincipal principal)
|
||||
{
|
||||
if (principal == null)
|
||||
throw new ArgumentNullException(nameof(principal));
|
||||
|
||||
var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
if (String.IsNullOrEmpty(loggedInUserId))
|
||||
throw new ArgumentNullException(nameof(loggedInUserId));
|
||||
|
||||
var result = TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(loggedInUserId);
|
||||
|
||||
if (result is null)
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
|
||||
return (T)result;
|
||||
|
||||
}
|
||||
}
|
@ -8,11 +8,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Persistence.Database.Postgres\Persistence.Database.Postgres.csproj" />
|
||||
<ProjectReference Include="..\Persistence.Repository\Persistence.Repository.csproj" />
|
||||
<ProjectReference Include="..\Persistence\Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
|
@ -1,10 +1,5 @@
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Persistence.Repositories;
|
||||
using Persistence.Repository;
|
||||
using Persistence.Repository.Data;
|
||||
using Persistence.Repository.Repositories;
|
||||
using Persistence.Models;
|
||||
|
||||
namespace Persistence.API;
|
||||
|
||||
@ -12,8 +7,9 @@ public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
Persistence.Repository.Startup.BeforeRunHandler(host);
|
||||
Startup.BeforeRunHandler(host);
|
||||
host.Run();
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5032"
|
||||
"applicationUrl": "http://localhost:13616"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Persistence.Repository;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Database.Postgres;
|
||||
|
||||
namespace Persistence.API;
|
||||
|
||||
@ -17,9 +19,10 @@ public class Startup
|
||||
services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen();
|
||||
|
||||
services.AddInfrastructure(Configuration);
|
||||
services.AddSwagger(Configuration);
|
||||
services.AddInfrastructure();
|
||||
services.AddPersistenceDbContext(Configuration);
|
||||
services.AddJWTAuthentication(Configuration);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
@ -30,11 +33,28 @@ public class Startup
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
//app.UseAuthorization();
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
}
|
||||
|
||||
public static void BeforeRunHandler(IHost host)
|
||||
{
|
||||
using var scope = host.Services.CreateScope();
|
||||
var provider = scope.ServiceProvider;
|
||||
|
||||
var context = provider.GetRequiredService<PersistenceDbContext>();
|
||||
context.Database.EnsureCreatedAndMigrated();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
namespace Persistence.API;
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
@ -4,5 +4,6 @@
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NeedUseKeyCloak": false
|
||||
}
|
||||
|
@ -1,8 +1,16 @@
|
||||
{
|
||||
{
|
||||
"DbConnection": {
|
||||
"Host": "localhost",
|
||||
"Port": 5432,
|
||||
"Username": "postgres",
|
||||
"Password": "q"
|
||||
}
|
||||
},
|
||||
"NeedUseKeyCloak": false,
|
||||
"AuthUser": {
|
||||
"username": "myuser",
|
||||
"password": 12345,
|
||||
"clientId": "webapi",
|
||||
"grantType": "password"
|
||||
},
|
||||
"KeycloakGetTokenUrl": "http://192.168.0.10:8321/realms/Persistence/protocol/openid-connect/token"
|
||||
}
|
||||
|
@ -6,7 +6,13 @@
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Host=localhost;Database=persistence;Username=postgres;Password=q;Persist Security Info=True",
|
||||
"DefaultConnection": "Host=localhost;Database=persistence;Username=postgres;Password=q;Persist Security Info=True"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"Authentication": {
|
||||
"MetadataAddress": "http://192.168.0.10:8321/realms/Persistence/.well-known/openid-configuration",
|
||||
"Audience": "account",
|
||||
"ValidIssuer": "http://192.168.0.10:8321/realms/Persistence",
|
||||
"AuthorizationUrl": "http://192.168.0.10:8321/realms/Persistence/protocol/openid-connect/auth"
|
||||
}
|
||||
}
|
||||
|
24
Persistence.Client/Clients/ISetpointClient.cs
Normal file
24
Persistence.Client/Clients/ISetpointClient.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Persistence.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Persistence.Client.Clients;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для тестирования API, предназначенного для работы с уставками
|
||||
/// </summary>
|
||||
public interface ISetpointClient
|
||||
{
|
||||
private const string BaseRoute = "/api/setpoint";
|
||||
|
||||
[Get($"{BaseRoute}/current")]
|
||||
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys);
|
||||
|
||||
[Get($"{BaseRoute}/history")]
|
||||
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetHistory([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, [Query] DateTimeOffset historyMoment);
|
||||
|
||||
[Get($"{BaseRoute}/log")]
|
||||
Task<IApiResponse<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys);
|
||||
|
||||
[Post($"{BaseRoute}/")]
|
||||
Task<IApiResponse> Save(Guid setpointKey, object newValue);
|
||||
}
|
22
Persistence.Client/Clients/ITimeSeriesClient.cs
Normal file
22
Persistence.Client/Clients/ITimeSeriesClient.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.Models;
|
||||
using Refit;
|
||||
|
||||
namespace Persistence.Client.Clients;
|
||||
public interface ITimeSeriesClient<TDto>
|
||||
where TDto : class, new()
|
||||
{
|
||||
private const string BaseRoute = "/api/dataSaub";
|
||||
|
||||
[Post($"{BaseRoute}")]
|
||||
Task<IApiResponse<int>> InsertRange(IEnumerable<TDto> dtos);
|
||||
|
||||
[Get($"{BaseRoute}")]
|
||||
Task<IApiResponse<IEnumerable<TDto>>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd);
|
||||
|
||||
[Get($"{BaseRoute}/resampled")]
|
||||
Task<IApiResponse<IEnumerable<TDto>>> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024);
|
||||
|
||||
[Get($"{BaseRoute}/datesRange")]
|
||||
Task<IApiResponse<DatesRangeDto?>> GetDatesRange();
|
||||
}
|
72
Persistence.Client/Helpers/ApiTokenHelper.cs
Normal file
72
Persistence.Client/Helpers/ApiTokenHelper.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Persistence.Models.Configurations;
|
||||
using RestSharp;
|
||||
|
||||
namespace Persistence.Client.Helpers;
|
||||
public static class ApiTokenHelper
|
||||
{
|
||||
public static void Authorize(this HttpClient httpClient, IConfiguration configuration)
|
||||
{
|
||||
var authUser = configuration
|
||||
.GetSection(nameof(AuthUser))
|
||||
.Get<AuthUser>()!;
|
||||
var needUseKeyCloak = configuration
|
||||
.GetSection("NeedUseKeyCloak")
|
||||
.Get<bool>()!;
|
||||
var keycloakGetTokenUrl = configuration.GetSection("KeycloakGetTokenUrl").Get<string>() ?? string.Empty;
|
||||
|
||||
var jwtToken = needUseKeyCloak
|
||||
? authUser.CreateKeyCloakJwtToken(keycloakGetTokenUrl)
|
||||
: authUser.CreateDefaultJwtToken();
|
||||
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
|
||||
}
|
||||
|
||||
private static string CreateDefaultJwtToken(this AuthUser authUser)
|
||||
{
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new("client_id", authUser.ClientId),
|
||||
new("username", authUser.Username),
|
||||
new("password", authUser.Password),
|
||||
new("grant_type", authUser.GrantType)
|
||||
};
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Issuer = JwtParams.Issuer,
|
||||
Audience = JwtParams.Audience,
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddHours(1),
|
||||
SigningCredentials = new SigningCredentials(JwtParams.SecurityKey, SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
private static string CreateKeyCloakJwtToken(this AuthUser authUser, string keycloakGetTokenUrl)
|
||||
{
|
||||
var restClient = new RestClient();
|
||||
|
||||
var request = new RestRequest(keycloakGetTokenUrl, Method.Post);
|
||||
request.AddParameter("username", authUser.Username);
|
||||
request.AddParameter("password", authUser.Password);
|
||||
request.AddParameter("client_id", authUser.ClientId);
|
||||
request.AddParameter("grant_type", authUser.GrantType);
|
||||
|
||||
var keyCloackResponse = restClient.Post(request);
|
||||
if (keyCloackResponse.IsSuccessful && !String.IsNullOrEmpty(keyCloackResponse.Content))
|
||||
{
|
||||
var token = JsonSerializer.Deserialize<JwtToken>(keyCloackResponse.Content)!;
|
||||
return token.AccessToken;
|
||||
}
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
25
Persistence.Client/Persistence.Client.csproj
Normal file
25
Persistence.Client/Persistence.Client.csproj
Normal file
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.1" />
|
||||
<PackageReference Include="Refit" Version="8.0.0" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Persistence\Persistence.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
32
Persistence.Client/PersistenceClientFactory.cs
Normal file
32
Persistence.Client/PersistenceClientFactory.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Persistence.Client.Helpers;
|
||||
using Refit;
|
||||
|
||||
namespace Persistence.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Фабрика клиентов для доступа к Persistence - сервису
|
||||
/// </summary>
|
||||
public class PersistenceClientFactory
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
private static readonly RefitSettings RefitSettings = new(new SystemTextJsonContentSerializer(JsonSerializerOptions));
|
||||
private HttpClient httpClient;
|
||||
public PersistenceClientFactory(IHttpClientFactory httpClientFactory, IConfiguration configuration)
|
||||
{
|
||||
this.httpClient = httpClientFactory.CreateClient();
|
||||
|
||||
httpClient.Authorize(configuration);
|
||||
}
|
||||
|
||||
public T GetClient<T>()
|
||||
{
|
||||
return RestService.For<T>(httpClient, RefitSettings);
|
||||
}
|
||||
}
|
||||
}
|
20
Persistence.Database.Postgres/DependencyInjection.cs
Normal file
20
Persistence.Database.Postgres/DependencyInjection.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Persistence.Database.Model;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddPersistenceDbContext(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
string connectionStringName = "DefaultConnection";
|
||||
|
||||
services.AddDbContext<PersistenceDbContext>(options =>
|
||||
options.UseNpgsql(configuration.GetConnectionString(connectionStringName)));
|
||||
|
||||
services.AddScoped<DbContext>(provider => provider.GetRequiredService<PersistenceDbContext>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Database;
|
||||
namespace Persistence.Database.Postgres;
|
||||
public static class EFExtensionsInitialization
|
||||
{
|
||||
public static void EnsureCreatedAndMigrated(this DatabaseFacade db)
|
146
Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs
generated
Normal file
146
Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs
generated
Normal file
@ -0,0 +1,146 @@
|
||||
// <auto-generated />
|
||||
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("20241118052225_SetpointMigration")]
|
||||
partial class SetpointMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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.Model.DataSaub", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<double?>("AxialLoad")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("axialLoad");
|
||||
|
||||
b.Property<double?>("BitDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("bitDepth");
|
||||
|
||||
b.Property<double?>("BlockPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockPosition");
|
||||
|
||||
b.Property<double?>("BlockSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockSpeed");
|
||||
|
||||
b.Property<double?>("Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("flow");
|
||||
|
||||
b.Property<double?>("HookWeight")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("hookWeight");
|
||||
|
||||
b.Property<int>("IdFeedRegulator")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("idFeedRegulator");
|
||||
|
||||
b.Property<int?>("Mode")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("mode");
|
||||
|
||||
b.Property<double?>("Mse")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("mse");
|
||||
|
||||
b.Property<short>("MseState")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("mseState");
|
||||
|
||||
b.Property<double?>("Pressure")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pressure");
|
||||
|
||||
b.Property<double?>("Pump0Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump0Flow");
|
||||
|
||||
b.Property<double?>("Pump1Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump1Flow");
|
||||
|
||||
b.Property<double?>("Pump2Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump2Flow");
|
||||
|
||||
b.Property<double?>("RotorSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorSpeed");
|
||||
|
||||
b.Property<double?>("RotorTorque")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorTorque");
|
||||
|
||||
b.Property<int>("TimeStamp")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("timestamp");
|
||||
|
||||
b.Property<string>("User")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("user");
|
||||
|
||||
b.Property<double?>("WellDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("wellDepth");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DataSaub");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Model.Setpoint", b =>
|
||||
{
|
||||
b.Property<Guid>("Key")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Ключ");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("Дата изменения уставки");
|
||||
|
||||
b.Property<int>("IdUser")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("Id автора последнего изменения");
|
||||
|
||||
b.Property<object>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasComment("Значение уставки");
|
||||
|
||||
b.HasKey("Key", "Created");
|
||||
|
||||
b.ToTable("Setpoint");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.Database.Postgres.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SetpointMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Setpoint",
|
||||
columns: table => new
|
||||
{
|
||||
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 автора последнего изменения")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Setpoint", x => new { x.Key, x.Created });
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Setpoint");
|
||||
}
|
||||
}
|
||||
}
|
115
Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs
generated
Normal file
115
Persistence.Database.Postgres/Migrations/20241122074646_InitialCreate.Designer.cs
generated
Normal file
@ -0,0 +1,115 @@
|
||||
// <auto-generated />
|
||||
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("20241122074646_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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.Model.DataSaub", b =>
|
||||
{
|
||||
b.Property<DateTimeOffset>("Date")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("date");
|
||||
|
||||
b.Property<double?>("AxialLoad")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("axialLoad");
|
||||
|
||||
b.Property<double?>("BitDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("bitDepth");
|
||||
|
||||
b.Property<double?>("BlockPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockPosition");
|
||||
|
||||
b.Property<double?>("BlockSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockSpeed");
|
||||
|
||||
b.Property<double?>("Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("flow");
|
||||
|
||||
b.Property<double?>("HookWeight")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("hookWeight");
|
||||
|
||||
b.Property<int>("IdFeedRegulator")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("idFeedRegulator");
|
||||
|
||||
b.Property<int?>("Mode")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("mode");
|
||||
|
||||
b.Property<double?>("Mse")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("mse");
|
||||
|
||||
b.Property<short>("MseState")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("mseState");
|
||||
|
||||
b.Property<double?>("Pressure")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pressure");
|
||||
|
||||
b.Property<double?>("Pump0Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump0Flow");
|
||||
|
||||
b.Property<double?>("Pump1Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump1Flow");
|
||||
|
||||
b.Property<double?>("Pump2Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump2Flow");
|
||||
|
||||
b.Property<double?>("RotorSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorSpeed");
|
||||
|
||||
b.Property<double?>("RotorTorque")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorTorque");
|
||||
|
||||
b.Property<string>("User")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("user");
|
||||
|
||||
b.Property<double?>("WellDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("wellDepth");
|
||||
|
||||
b.HasKey("Date");
|
||||
|
||||
b.ToTable("DataSaub");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.Database.Postgres.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:PostgresExtension:adminpack", ",,");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DataSaub",
|
||||
columns: table => new
|
||||
{
|
||||
date = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
mode = table.Column<int>(type: "integer", nullable: true),
|
||||
user = table.Column<string>(type: "text", nullable: true),
|
||||
wellDepth = table.Column<double>(type: "double precision", nullable: true),
|
||||
bitDepth = table.Column<double>(type: "double precision", nullable: true),
|
||||
blockPosition = table.Column<double>(type: "double precision", nullable: true),
|
||||
blockSpeed = table.Column<double>(type: "double precision", nullable: true),
|
||||
pressure = table.Column<double>(type: "double precision", nullable: true),
|
||||
axialLoad = table.Column<double>(type: "double precision", nullable: true),
|
||||
hookWeight = table.Column<double>(type: "double precision", nullable: true),
|
||||
rotorTorque = table.Column<double>(type: "double precision", nullable: true),
|
||||
rotorSpeed = table.Column<double>(type: "double precision", nullable: true),
|
||||
flow = table.Column<double>(type: "double precision", nullable: true),
|
||||
mseState = table.Column<short>(type: "smallint", nullable: false),
|
||||
idFeedRegulator = table.Column<int>(type: "integer", nullable: false),
|
||||
mse = table.Column<double>(type: "double precision", nullable: true),
|
||||
pump0Flow = table.Column<double>(type: "double precision", nullable: true),
|
||||
pump1Flow = table.Column<double>(type: "double precision", nullable: true),
|
||||
pump2Flow = table.Column<double>(type: "double precision", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DataSaub", x => x.date);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DataSaub");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Persistence.Database.Model;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Persistence.Database.Postgres.Migrations
|
||||
{
|
||||
[DbContext(typeof(PersistenceDbContext))]
|
||||
partial class PersistenceDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(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.Model.DataSaub", b =>
|
||||
{
|
||||
b.Property<DateTimeOffset>("Date")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("date");
|
||||
|
||||
b.Property<double?>("AxialLoad")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("axialLoad");
|
||||
|
||||
b.Property<double?>("BitDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("bitDepth");
|
||||
|
||||
b.Property<double?>("BlockPosition")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockPosition");
|
||||
|
||||
b.Property<double?>("BlockSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("blockSpeed");
|
||||
|
||||
b.Property<double?>("Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("flow");
|
||||
|
||||
b.Property<double?>("HookWeight")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("hookWeight");
|
||||
|
||||
b.Property<int>("IdFeedRegulator")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("idFeedRegulator");
|
||||
|
||||
b.Property<int?>("Mode")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("mode");
|
||||
|
||||
b.Property<double?>("Mse")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("mse");
|
||||
|
||||
b.Property<short>("MseState")
|
||||
.HasColumnType("smallint")
|
||||
.HasColumnName("mseState");
|
||||
|
||||
b.Property<double?>("Pressure")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pressure");
|
||||
|
||||
b.Property<double?>("Pump0Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump0Flow");
|
||||
|
||||
b.Property<double?>("Pump1Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump1Flow");
|
||||
|
||||
b.Property<double?>("Pump2Flow")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("pump2Flow");
|
||||
|
||||
b.Property<double?>("RotorSpeed")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorSpeed");
|
||||
|
||||
b.Property<double?>("RotorTorque")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("rotorTorque");
|
||||
|
||||
b.Property<string>("User")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("user");
|
||||
|
||||
b.Property<double?>("WellDepth")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("wellDepth");
|
||||
|
||||
b.HasKey("Date");
|
||||
|
||||
b.ToTable("DataSaub");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Persistence.Database.Model.Setpoint", b =>
|
||||
{
|
||||
b.Property<Guid>("Key")
|
||||
.HasColumnType("uuid")
|
||||
.HasComment("Ключ");
|
||||
|
||||
b.Property<DateTimeOffset>("Created")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasComment("Дата изменения уставки");
|
||||
|
||||
b.Property<int>("IdUser")
|
||||
.HasColumnType("integer")
|
||||
.HasComment("Id автора последнего изменения");
|
||||
|
||||
b.Property<object>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasComment("Значение уставки");
|
||||
|
||||
b.HasKey("Key", "Created");
|
||||
|
||||
b.ToTable("Setpoint");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Persistence.Database\Persistence.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,19 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace Persistence.Database.Model;
|
||||
public partial class PersistenceDbContext : DbContext, IPersistenceDbContext
|
||||
{
|
||||
public DbSet<DataSaub> DataSaub => Set<DataSaub>();
|
||||
|
||||
public PersistenceDbContext(DbContextOptions<PersistenceDbContext> options)
|
||||
public DbSet<Setpoint> Setpoint => Set<Setpoint>();
|
||||
|
||||
public PersistenceDbContext()
|
||||
: base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public PersistenceDbContext(DbContextOptions<PersistenceDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
@ -30,5 +34,5 @@ public partial class PersistenceDbContext : DbContext, IPersistenceDbContext
|
||||
.HasAnnotation("Relational:Collation", "Russian_Russia.1251");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
5
Persistence.Database.Postgres/Readme.md
Normal file
5
Persistence.Database.Postgres/Readme.md
Normal file
@ -0,0 +1,5 @@
|
||||
## Создать миграцию
|
||||
```
|
||||
dotnet ef migrations add <MigrationName> --project Persistence.Database.Postgres
|
||||
|
||||
```
|
@ -1,13 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Persistence.Database.Model;
|
||||
public class DataSaub : ITimestampedData
|
||||
{
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("timestamp")]
|
||||
public int TimeStamp { get; set; }
|
||||
[Key, Column("date")]
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
[Column("mode")]
|
||||
public int? Mode { get; set; }
|
@ -7,5 +7,8 @@ using System.Threading.Tasks;
|
||||
namespace Persistence.Database.Model;
|
||||
public interface ITimestampedData
|
||||
{
|
||||
int TimeStamp { get; set; }
|
||||
/// <summary>
|
||||
/// Дата (должна быть обязательно в UTC)
|
||||
/// </summary>
|
||||
DateTimeOffset Date { get; set; }
|
||||
}
|
21
Persistence.Database/Entity/Setpoint.cs
Normal file
21
Persistence.Database/Entity/Setpoint.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Persistence.Database.Model
|
||||
{
|
||||
[PrimaryKey(nameof(Key), nameof(Created))]
|
||||
public class Setpoint
|
||||
{
|
||||
[Comment("Ключ")]
|
||||
public Guid Key { get; set; }
|
||||
|
||||
[Column(TypeName = "jsonb"), Comment("Значение уставки")]
|
||||
public required object Value { get; set; }
|
||||
|
||||
[Comment("Дата создания уставки")]
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
[Comment("Id автора последнего изменения")]
|
||||
public int IdUser { get; set; }
|
||||
}
|
||||
}
|
8
Persistence.Database/IPersistenceDbContext.cs
Normal file
8
Persistence.Database/IPersistenceDbContext.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Persistence.Database.Model;
|
||||
|
||||
namespace Persistence.Database;
|
||||
public interface IPersistenceDbContext : IDisposable
|
||||
{
|
||||
DbSet<DataSaub> DataSaub { get; }
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Database.Model;
|
||||
public interface IDbContextManager
|
||||
{
|
||||
//IConnectionManager ConnectionManager { get; }
|
||||
|
||||
DbContext GetReadonlyDbContext();
|
||||
|
||||
DbContext GetDbContext();
|
||||
|
||||
DbContext CreateAndInitializeNewContext();
|
||||
|
||||
DbContext CreateAndInitializeNewContext(DbConnection connection);
|
||||
}
|
@ -11,6 +11,7 @@ namespace Persistence.Database.Model;
|
||||
public interface IPersistenceDbContext : IDisposable
|
||||
{
|
||||
DbSet<DataSaub> DataSaub { get; }
|
||||
DatabaseFacade Database { get; }
|
||||
DbSet<Setpoint> Setpoint { get; }
|
||||
DatabaseFacade Database { get; }
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
@ -7,11 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,43 +0,0 @@
|
||||
namespace Persistence.IntegrationTests;
|
||||
public static class ApiTokenHelper
|
||||
{
|
||||
//public static string GetAdminUserToken()
|
||||
//{
|
||||
// var user = new User()
|
||||
// {
|
||||
// Id = 1,
|
||||
// IdCompany = 1,
|
||||
// Login = "test_user"
|
||||
// };
|
||||
// var roles = new[] { "root" };
|
||||
|
||||
// return CreateToken(user, roles);
|
||||
//}
|
||||
|
||||
//private static string CreateToken(User user, IEnumerable<string> roles)
|
||||
//{
|
||||
// var claims = new List<Claim>
|
||||
// {
|
||||
// new("id", user.Id.ToString()),
|
||||
// new(ClaimsIdentity.DefaultNameClaimType, user.Login),
|
||||
// new("idCompany", user.IdCompany.ToString()),
|
||||
// };
|
||||
|
||||
// claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
||||
|
||||
// const string secret = "супер секретный ключ для шифрования";
|
||||
|
||||
// var key = Encoding.ASCII.GetBytes(secret);
|
||||
// var tokenDescriptor = new SecurityTokenDescriptor
|
||||
// {
|
||||
// Issuer = "a",
|
||||
// Audience = "a",
|
||||
// Subject = new ClaimsIdentity(claims),
|
||||
// Expires = DateTime.UtcNow.AddHours(1),
|
||||
// SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
// };
|
||||
// var tokenHandler = new JwtSecurityTokenHandler();
|
||||
// var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
// return tokenHandler.WriteToken(token);
|
||||
//}
|
||||
}
|
@ -17,9 +17,9 @@ public abstract class BaseIntegrationTest : IClassFixture<WebAppFactoryFixture>,
|
||||
|
||||
protected BaseIntegrationTest(WebAppFactoryFixture factory)
|
||||
{
|
||||
//scope = factory.Services.CreateScope();
|
||||
scope = factory.Services.CreateScope();
|
||||
|
||||
//dbContext = scope.ServiceProvider.GetRequiredService<PersistenceDbContext>();
|
||||
dbContext = scope.ServiceProvider.GetRequiredService<PersistenceDbContext>();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -1,16 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.Repository.Data;
|
||||
using Refit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.IntegrationTests.Clients;
|
||||
public interface ITimeSeriesClient<TDto>
|
||||
where TDto : class, new()
|
||||
{
|
||||
[Post("/api/dataSaub")]
|
||||
Task<IApiResponse<int>> InsertRangeAsync(IEnumerable<TDto> dtos);
|
||||
}
|
@ -1,36 +1,55 @@
|
||||
using Persistence.Repository.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Persistence.Client;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Repository.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Persistence.IntegrationTests.Controllers;
|
||||
public class DataSaubControllerTest : TimeSeriesBaseControllerTest<DataSaubDto>
|
||||
public class DataSaubControllerTest : TimeSeriesBaseControllerTest<DataSaub, DataSaubDto>
|
||||
{
|
||||
private readonly DataSaubDto dto = new DataSaubDto()
|
||||
{
|
||||
AxialLoad = 1,
|
||||
BitDepth = 2,
|
||||
BlockPosition = 3,
|
||||
BlockSpeed = 4,
|
||||
Date = DateTimeOffset.Now,
|
||||
Flow = 5,
|
||||
HookWeight = 6,
|
||||
Id = 7,
|
||||
IdFeedRegulator = 8,
|
||||
Mode = 9,
|
||||
Mse = 10,
|
||||
MseState = 11,
|
||||
Pressure = 12,
|
||||
Pump0Flow = 13,
|
||||
Pump1Flow = 14,
|
||||
Pump2Flow = 15,
|
||||
RotorSpeed = 16,
|
||||
RotorTorque = 17,
|
||||
User = string.Empty,
|
||||
WellDepth = 18,
|
||||
AxialLoad = 1,
|
||||
BitDepth = 2,
|
||||
BlockPosition = 3,
|
||||
BlockSpeed = 4,
|
||||
Date = DateTimeOffset.UtcNow,
|
||||
Flow = 5,
|
||||
HookWeight = 6,
|
||||
IdFeedRegulator = 8,
|
||||
Mode = 9,
|
||||
Mse = 10,
|
||||
MseState = 11,
|
||||
Pressure = 12,
|
||||
Pump0Flow = 13,
|
||||
Pump1Flow = 14,
|
||||
Pump2Flow = 15,
|
||||
RotorSpeed = 16,
|
||||
RotorTorque = 17,
|
||||
User = string.Empty,
|
||||
WellDepth = 18,
|
||||
};
|
||||
|
||||
private readonly DataSaub entity = new DataSaub()
|
||||
{
|
||||
AxialLoad = 1,
|
||||
BitDepth = 2,
|
||||
BlockPosition = 3,
|
||||
BlockSpeed = 4,
|
||||
Date = DateTimeOffset.UtcNow,
|
||||
Flow = 5,
|
||||
HookWeight = 6,
|
||||
IdFeedRegulator = 8,
|
||||
Mode = 9,
|
||||
Mse = 10,
|
||||
MseState = 11,
|
||||
Pressure = 12,
|
||||
Pump0Flow = 13,
|
||||
Pump1Flow = 14,
|
||||
Pump2Flow = 15,
|
||||
RotorSpeed = 16,
|
||||
RotorTorque = 17,
|
||||
User = string.Empty,
|
||||
WellDepth = 18,
|
||||
};
|
||||
|
||||
public DataSaubControllerTest(WebAppFactoryFixture factory) : base(factory)
|
||||
@ -42,4 +61,26 @@ public class DataSaubControllerTest : TimeSeriesBaseControllerTest<DataSaubDto>
|
||||
{
|
||||
await InsertRangeSuccess(dto);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Get_returns_success()
|
||||
{
|
||||
var beginDate = DateTimeOffset.UtcNow.AddDays(-1);
|
||||
var endDate = DateTimeOffset.UtcNow;
|
||||
await GetSuccess(beginDate, endDate, entity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDatesRange_returns_success()
|
||||
{
|
||||
await GetDatesRangeSuccess(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task GetResampledData_returns_success()
|
||||
{
|
||||
await GetResampledDataSuccess(entity);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,159 @@
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Persistence.Client;
|
||||
using Persistence.Client.Clients;
|
||||
using Xunit;
|
||||
|
||||
namespace Persistence.IntegrationTests.Controllers
|
||||
{
|
||||
public class SetpointControllerTest : BaseIntegrationTest
|
||||
{
|
||||
private ISetpointClient setpointClient;
|
||||
private class TestObject
|
||||
{
|
||||
public string? value1 { get; set; }
|
||||
public int? value2 { get; set; }
|
||||
}
|
||||
public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory)
|
||||
{
|
||||
var scope = factory.Services.CreateScope();
|
||||
var persistenceClientFactory = scope.ServiceProvider
|
||||
.GetRequiredService<PersistenceClientFactory>();
|
||||
|
||||
setpointClient = persistenceClientFactory.GetClient<ISetpointClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCurrent_returns_success()
|
||||
{
|
||||
//arrange
|
||||
var setpointKeys = new List<Guid>()
|
||||
{
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid()
|
||||
};
|
||||
|
||||
//act
|
||||
var response = await setpointClient.GetCurrent(setpointKeys);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.Empty(response.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCurrent_AfterSave_returns_success()
|
||||
{
|
||||
//arrange
|
||||
var setpointKey = await Save();
|
||||
|
||||
//act
|
||||
var response = await setpointClient.GetCurrent([setpointKey]);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.NotEmpty(response.Content);
|
||||
Assert.Equal(setpointKey, response.Content.FirstOrDefault()?.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetHistory_returns_success()
|
||||
{
|
||||
//arrange
|
||||
var setpointKeys = new List<Guid>()
|
||||
{
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid()
|
||||
};
|
||||
var historyMoment = DateTimeOffset.UtcNow;
|
||||
|
||||
//act
|
||||
var response = await setpointClient.GetHistory(setpointKeys, historyMoment);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.Empty(response.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetHistory_AfterSave_returns_success()
|
||||
{
|
||||
//arrange
|
||||
var setpointKey = await Save();
|
||||
var historyMoment = DateTimeOffset.UtcNow;
|
||||
historyMoment = historyMoment.AddDays(1);
|
||||
|
||||
//act
|
||||
var response = await setpointClient.GetHistory([setpointKey], historyMoment);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.NotEmpty(response.Content);
|
||||
Assert.Equal(setpointKey, response.Content.FirstOrDefault()?.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetLog_returns_success()
|
||||
{
|
||||
//arrange
|
||||
var setpointKeys = new List<Guid>()
|
||||
{
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid()
|
||||
};
|
||||
|
||||
//act
|
||||
var response = await setpointClient.GetLog(setpointKeys);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.Empty(response.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetLog_AfterSave_returns_success()
|
||||
{
|
||||
//arrange
|
||||
var setpointKey = await Save();
|
||||
|
||||
//act
|
||||
var response = await setpointClient.GetLog([setpointKey]);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.NotEmpty(response.Content);
|
||||
Assert.Equal(setpointKey, response.Content.FirstOrDefault().Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Save_returns_success()
|
||||
{
|
||||
await Save();
|
||||
}
|
||||
|
||||
private async Task<Guid> Save()
|
||||
{
|
||||
//arrange
|
||||
var setpointKey = Guid.NewGuid();
|
||||
var setpointValue = new TestObject()
|
||||
{
|
||||
value1 = "1",
|
||||
value2 = 2
|
||||
};
|
||||
|
||||
//act
|
||||
var response = await setpointClient.Save(setpointKey, setpointValue);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
return setpointKey;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +1,27 @@
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Persistence.IntegrationTests.Clients;
|
||||
using Persistence.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Mapster;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Persistence.Client;
|
||||
using Persistence.Client.Clients;
|
||||
using Persistence.Database.Model;
|
||||
using Xunit;
|
||||
|
||||
namespace Persistence.IntegrationTests.Controllers;
|
||||
public abstract class TimeSeriesBaseControllerTest<TDto> : BaseIntegrationTest
|
||||
public abstract class TimeSeriesBaseControllerTest<TEntity, TDto> : BaseIntegrationTest
|
||||
where TEntity : class, ITimestampedData, new()
|
||||
where TDto : class, new()
|
||||
{
|
||||
private ITimeSeriesClient<TDto> client;
|
||||
private ITimeSeriesClient<TDto> timeSeriesClient;
|
||||
|
||||
public TimeSeriesBaseControllerTest(WebAppFactoryFixture factory) : base(factory)
|
||||
{
|
||||
//dbContext.CleanupDbSet<TEntity>();
|
||||
client = factory.GetHttpClient<ITimeSeriesClient<TDto>>(string.Empty);
|
||||
dbContext.CleanupDbSet<TEntity>();
|
||||
|
||||
var scope = factory.Services.CreateScope();
|
||||
var persistenceClientFactory = scope.ServiceProvider
|
||||
.GetRequiredService<PersistenceClientFactory>();
|
||||
|
||||
timeSeriesClient = persistenceClientFactory.GetClient<ITimeSeriesClient<TDto>>();
|
||||
}
|
||||
|
||||
public async Task InsertRangeSuccess(TDto dto)
|
||||
@ -29,23 +30,96 @@ public abstract class TimeSeriesBaseControllerTest<TDto> : BaseIntegrationTest
|
||||
var expected = dto.Adapt<TDto>();
|
||||
|
||||
//act
|
||||
var response = await client.InsertRangeAsync(new TDto[] { expected });
|
||||
var response = await timeSeriesClient.InsertRange(new TDto[] { expected });
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(1, response.Content);
|
||||
|
||||
//var entity = GetByWellId();
|
||||
|
||||
//Assert.NotNull(entity);
|
||||
|
||||
//var actual = entity.Adapt<ChangeLogDto<TDto>>();
|
||||
//Assert.Equal(ProcessMapPlanBase.IdStateActual, actual.IdState);
|
||||
|
||||
//var excludeProps = new[] {
|
||||
// nameof(ProcessMapPlanBaseDto.Id),
|
||||
// nameof(ProcessMapPlanBaseDto.Section)
|
||||
//};
|
||||
//MatchHelper.Match(expected, actual.Item, excludeProps);
|
||||
}
|
||||
|
||||
public async Task GetSuccess(DateTimeOffset beginDate, DateTimeOffset endDate, TEntity entity)
|
||||
{
|
||||
//arrange
|
||||
var dbset = dbContext.Set<TEntity>();
|
||||
|
||||
dbset.Add(entity);
|
||||
|
||||
dbContext.SaveChanges();
|
||||
|
||||
var response = await timeSeriesClient.Get(beginDate, endDate);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
Assert.Single(response.Content);
|
||||
}
|
||||
|
||||
public async Task GetDatesRangeSuccess(TEntity entity)
|
||||
{
|
||||
//arrange
|
||||
var datesRangeExpected = 30;
|
||||
|
||||
var entity2 = entity.Adapt<TEntity>();
|
||||
entity2.Date = entity.Date.AddDays(datesRangeExpected);
|
||||
|
||||
var dbset = dbContext.Set<TEntity>();
|
||||
dbset.Add(entity);
|
||||
dbset.Add(entity2);
|
||||
|
||||
dbContext.SaveChanges();
|
||||
|
||||
var response = await timeSeriesClient.GetDatesRange();
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
|
||||
var datesRangeActual = (response.Content.To - response.Content.From).Days;
|
||||
Assert.Equal(datesRangeExpected, datesRangeActual);
|
||||
}
|
||||
|
||||
public async Task GetResampledDataSuccess(TEntity entity)
|
||||
{
|
||||
//arrange
|
||||
var approxPointsCount = 10;
|
||||
var differenceBetweenStartAndEndDays = 50;
|
||||
|
||||
var entities = new List<TEntity>();
|
||||
for (var i = 1; i <= differenceBetweenStartAndEndDays; i++)
|
||||
{
|
||||
var entity2 = entity.Adapt<TEntity>();
|
||||
entity2.Date = entity.Date.AddDays(i - 1);
|
||||
|
||||
entities.Add(entity2);
|
||||
}
|
||||
|
||||
var dbset = dbContext.Set<TEntity>();
|
||||
dbset.AddRange(entities);
|
||||
|
||||
dbContext.SaveChanges();
|
||||
|
||||
var response = await timeSeriesClient.GetResampledData(entity.Date.AddMinutes(-1), differenceBetweenStartAndEndDays * 24 * 60 * 60 + 60, approxPointsCount);
|
||||
|
||||
//assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.NotNull(response.Content);
|
||||
|
||||
var ratio = entities.Count() / approxPointsCount;
|
||||
if (ratio > 1)
|
||||
{
|
||||
var expectedResampledCount = entities
|
||||
.Where((_, index) => index % ratio == 0)
|
||||
.Count();
|
||||
|
||||
Assert.Equal(expectedResampledCount, response.Content.Count());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(entities.Count(), response.Content.Count());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
14
Persistence.IntegrationTests/EFCoreExtensions.cs
Normal file
14
Persistence.IntegrationTests/EFCoreExtensions.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Persistence.Database.Model;
|
||||
|
||||
namespace Persistence.IntegrationTests;
|
||||
public static class EFCoreExtensions
|
||||
{
|
||||
public static void CleanupDbSet<T>(this DbContext dbContext)
|
||||
where T : class
|
||||
{
|
||||
var dbset = dbContext.Set<T>();
|
||||
dbset.RemoveRange(dbset);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="Refit" Version="8.0.0" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@ -24,6 +25,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Persistence.API\Persistence.API.csproj" />
|
||||
<ProjectReference Include="..\Persistence.Client\Persistence.Client.csproj" />
|
||||
<ProjectReference Include="..\Persistence.Database.Postgres\Persistence.Database.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
19
Persistence.IntegrationTests/TestHttpClientFactory.cs
Normal file
19
Persistence.IntegrationTests/TestHttpClientFactory.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace Persistence.IntegrationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Фабрика HTTP клиентов для интеграционных тестов
|
||||
/// </summary>
|
||||
public class TestHttpClientFactory : IHttpClientFactory
|
||||
{
|
||||
private readonly WebAppFactoryFixture factory;
|
||||
|
||||
public TestHttpClientFactory(WebAppFactoryFixture factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
public HttpClient CreateClient(string name)
|
||||
{
|
||||
return factory.CreateClient();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +1,55 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Persistence.Database.Model;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Persistence.API;
|
||||
using Refit;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Persistence.Client;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Database.Postgres;
|
||||
using RestSharp;
|
||||
|
||||
namespace Persistence.IntegrationTests;
|
||||
public class WebAppFactoryFixture : WebApplicationFactory<Startup>
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
//Converters = { new ValidationResultConverter() }
|
||||
};
|
||||
|
||||
private static readonly RefitSettings RefitSettings = new(new SystemTextJsonContentSerializer(JsonSerializerOptions));
|
||||
|
||||
private readonly string connectionString;
|
||||
|
||||
public WebAppFactoryFixture()
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.Tests.json")
|
||||
.Build();
|
||||
|
||||
var dbConnection = configuration.GetSection("DbConnection").Get<DbConnection>()!;
|
||||
connectionString = dbConnection.GetConnectionString();
|
||||
}
|
||||
private string connectionString = string.Empty;
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
builder.ConfigureAppConfiguration((hostingContext, config) =>
|
||||
{
|
||||
config.AddJsonFile("appsettings.Tests.json");
|
||||
|
||||
var dbConnection = config.Build().GetSection("DbConnection").Get<DbConnection>()!;
|
||||
connectionString = dbConnection.GetConnectionString();
|
||||
});
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<PersistenceDbContext>));
|
||||
|
||||
if (descriptor != null)
|
||||
services.Remove(descriptor);
|
||||
|
||||
services.AddDbContext<PersistenceDbContext>(options =>
|
||||
services.AddDbContext<PersistenceDbContext>(options =>
|
||||
options.UseNpgsql(connectionString));
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
services.RemoveAll<IHttpClientFactory>();
|
||||
services.AddSingleton<IHttpClientFactory>(provider =>
|
||||
{
|
||||
return new TestHttpClientFactory(this);
|
||||
});
|
||||
|
||||
services.AddSingleton<PersistenceClientFactory>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var scopedServices = scope.ServiceProvider;
|
||||
var dbContext = scopedServices.GetRequiredService<PersistenceDbContext>();
|
||||
|
||||
//dbContext.Database.EnsureCreatedAndMigrated();
|
||||
//dbContext.Deposits.AddRange(Data.Defaults.Deposits);
|
||||
var dbContext = scopedServices.GetRequiredService<PersistenceDbContext>();
|
||||
dbContext.Database.EnsureCreatedAndMigrated();
|
||||
dbContext.SaveChanges();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
@ -66,36 +61,4 @@ public class WebAppFactoryFixture : WebApplicationFactory<Startup>
|
||||
|
||||
await dbContext.Database.EnsureDeletedAsync();
|
||||
}
|
||||
|
||||
public T GetHttpClient<T>(string uriSuffix)
|
||||
{
|
||||
var httpClient = CreateClient();
|
||||
if (string.IsNullOrEmpty(uriSuffix))
|
||||
return RestService.For<T>(httpClient, RefitSettings);
|
||||
|
||||
if (httpClient.BaseAddress is not null)
|
||||
httpClient.BaseAddress = new Uri(httpClient.BaseAddress, uriSuffix);
|
||||
|
||||
return RestService.For<T>(httpClient, RefitSettings);
|
||||
}
|
||||
|
||||
//public T GetAuthorizedHttpClient<T>(string uriSuffix)
|
||||
//{
|
||||
// var httpClient = GetAuthorizedHttpClient();
|
||||
// if (string.IsNullOrEmpty(uriSuffix))
|
||||
// return RestService.For<T>(httpClient, RefitSettings);
|
||||
|
||||
// if (httpClient.BaseAddress is not null)
|
||||
// httpClient.BaseAddress = new Uri(httpClient.BaseAddress, uriSuffix);
|
||||
|
||||
// return RestService.For<T>(httpClient, RefitSettings);
|
||||
//}
|
||||
|
||||
//private HttpClient GetAuthorizedHttpClient()
|
||||
//{
|
||||
// var httpClient = CreateClient();
|
||||
// var jwtToken = ApiTokenHelper.GetAdminUserToken();
|
||||
// httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
|
||||
// return httpClient;
|
||||
//}
|
||||
}
|
||||
|
199
Persistence.Repository/CyclicArray.cs
Normal file
199
Persistence.Repository/CyclicArray.cs
Normal file
@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Repository;
|
||||
/// <summary>
|
||||
/// Цикличный массив
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class CyclicArray<T> : IEnumerable<T>
|
||||
{
|
||||
readonly T[] array;
|
||||
int used, current = -1;
|
||||
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public CyclicArray(int capacity)
|
||||
{
|
||||
array = new T[capacity];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Количество элементов в массиве
|
||||
/// </summary>
|
||||
public int Count => used;
|
||||
|
||||
/// <summary>
|
||||
/// Добавить новый элемент<br/>
|
||||
/// Если capacity достигнуто, то вытеснит самый первый элемент
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Add(T item)
|
||||
{
|
||||
current = (++current) % array.Length;
|
||||
array[current] = item;
|
||||
if (used < array.Length)
|
||||
used++;
|
||||
UpdatedInvoke(current, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить новые элементы.<br/>
|
||||
/// Если capacity достигнуто, то вытеснит самые первые элементы.<br/>
|
||||
/// Не вызывает Updated!
|
||||
/// </summary>
|
||||
/// <param name="items"></param>
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
var capacity = array.Length;
|
||||
var newItems = items.TakeLast(capacity).ToArray();
|
||||
if (newItems.Length == capacity)
|
||||
{
|
||||
Array.Copy(newItems, array, capacity);
|
||||
current = capacity - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = (++current) % capacity;
|
||||
var countToEndOfArray = capacity - current;
|
||||
if (newItems.Length <= countToEndOfArray)
|
||||
{
|
||||
Array.Copy(newItems, 0, array, current, newItems.Length);
|
||||
current += newItems.Length - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var firstStepLength = countToEndOfArray;
|
||||
Array.Copy(newItems, 0, array, current, firstStepLength);
|
||||
var secondStepCount = newItems.Length - firstStepLength;
|
||||
Array.Copy(newItems, firstStepLength, array, 0, secondStepCount);
|
||||
current = secondStepCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (used < capacity)
|
||||
{
|
||||
used += newItems.Length;
|
||||
used = used > capacity ? capacity : used;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Индекс
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (used == 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
var i = (current + 1 + index) % used;
|
||||
return array[i];
|
||||
}
|
||||
set
|
||||
{
|
||||
var devider = used > 0 ? used : array.Length;
|
||||
var i = (current + 1 + index) % devider;
|
||||
array[i] = value;
|
||||
UpdatedInvoke(current, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// событие на изменение элемента в массиве
|
||||
/// </summary>
|
||||
public event EventHandler<(int index, T value)>? Updated;
|
||||
private void UpdatedInvoke(int index, T value)
|
||||
{
|
||||
Updated?.Invoke(this, (index, value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Агрегирование значения по всему массиву
|
||||
/// </summary>
|
||||
/// <typeparam name="Tout"></typeparam>
|
||||
/// <param name="func"></param>
|
||||
/// <param name="startValue"></param>
|
||||
/// <returns></returns>
|
||||
public Tout Aggregate<Tout>(Func<T, Tout, Tout> func, Tout startValue)
|
||||
{
|
||||
Tout result = startValue;
|
||||
for (int i = 0; i < used; i++)
|
||||
result = func(this[i], result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
=> new CyclycListEnumerator<T>(array, current, used);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
class CyclycListEnumerator<Te> : IEnumerator<Te>
|
||||
{
|
||||
private readonly Te[] array;
|
||||
private readonly int used;
|
||||
private readonly int first;
|
||||
private int current = -1;
|
||||
|
||||
public CyclycListEnumerator(Te[] array, int first, int used)
|
||||
{
|
||||
this.array = new Te[array.Length];
|
||||
array.CopyTo(this.array, 0);
|
||||
this.used = used;
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public Te Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsCurrentOk())
|
||||
{
|
||||
var i = (current + first + 1) % used;
|
||||
return array[i];
|
||||
}
|
||||
else
|
||||
return default!;
|
||||
}
|
||||
}
|
||||
|
||||
object? IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose() {; }
|
||||
|
||||
private bool IsCurrentOk() => current >= 0 && current < used;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (current < used)
|
||||
current++;
|
||||
return IsCurrentOk();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
current = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очистить весь массив
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
used = 0;
|
||||
current = -1;
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
namespace Persistence.Repository.Data;
|
||||
public class DataSaubDto : ITimeSeriesAbstractDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public DateTimeOffset Date { get; set; } = DateTimeOffset.UtcNow;
|
||||
|
||||
public int? Mode { get; set; }
|
||||
|
28
Persistence.Repository/Data/SetpointDto.cs
Normal file
28
Persistence.Repository/Data/SetpointDto.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Persistence.Repository.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Модель для работы с уставкой
|
||||
/// </summary>
|
||||
public class SetpointDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор уставки
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение уставки
|
||||
/// </summary>
|
||||
public required object Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата сохранения уставки
|
||||
/// </summary>
|
||||
public DateTimeOffset Edit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ключ пользователя
|
||||
/// </summary>
|
||||
public int IdUser { get; set; }
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Persistence.Repositories;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Repositories;
|
||||
using Persistence.Repository.Data;
|
||||
using Persistence.Repository.Repositories;
|
||||
|
||||
@ -12,18 +10,13 @@ public static class DependencyInjection
|
||||
public static void MapsterSetup()
|
||||
{
|
||||
}
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
|
||||
{
|
||||
MapsterSetup();
|
||||
|
||||
string connectionStringName = "DefaultConnection";
|
||||
|
||||
services.AddDbContext<PersistenceDbContext>(options =>
|
||||
options.UseNpgsql(configuration.GetConnectionString(connectionStringName)));
|
||||
|
||||
services.AddScoped<IPersistenceDbContext>(provider => provider.GetRequiredService<PersistenceDbContext>());
|
||||
|
||||
services.AddTransient<ITimeSeriesDataRepository<DataSaubDto>, TimeSeriesDataRepository<DataSaub, DataSaubDto>>();
|
||||
services.AddTransient<ISetpointRepository, SetpointRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
73
Persistence.Repository/Repositories/SetpointRepository.cs
Normal file
73
Persistence.Repository/Repositories/SetpointRepository.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Models;
|
||||
using Persistence.Repositories;
|
||||
|
||||
namespace Persistence.Repository.Repositories
|
||||
{
|
||||
public class SetpointRepository : ISetpointRepository
|
||||
{
|
||||
private DbContext db;
|
||||
public SetpointRepository(DbContext db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
protected virtual IQueryable<Setpoint> GetQueryReadOnly() => db.Set<Setpoint>();
|
||||
|
||||
public async Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly();
|
||||
var entities = await query
|
||||
.Where(e => setpointKeys.Contains(e.Key))
|
||||
.ToArrayAsync(token);
|
||||
var dtos = entities.Select(e => e.Adapt<SetpointValueDto>());
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SetpointValueDto>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment, CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly();
|
||||
var entities = await query
|
||||
.Where(e => setpointKeys.Contains(e.Key))
|
||||
.ToArrayAsync(token);
|
||||
var filteredEntities = entities
|
||||
.GroupBy(e => e.Key)
|
||||
.Select(e => e.OrderBy(o => o.Created))
|
||||
.Select(e => e.Where(e => e.Created <= historyMoment).Last());
|
||||
var dtos = filteredEntities
|
||||
.Select(e => e.Adapt<SetpointValueDto>());
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly();
|
||||
var entities = await query
|
||||
.Where(e => setpointKeys.Contains(e.Key))
|
||||
.ToArrayAsync(token);
|
||||
var dtos = entities
|
||||
.GroupBy(e => e.Key)
|
||||
.ToDictionary(e => e.Key, v => v.Select(z => z.Adapt<SetpointLogDto>()));
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public async Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token)
|
||||
{
|
||||
var entity = new Setpoint()
|
||||
{
|
||||
Key = setpointKey,
|
||||
Value = newValue,
|
||||
IdUser = idUser,
|
||||
Created = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await db.Set<Setpoint>().AddAsync(entity, token);
|
||||
await db.SaveChangesAsync(token);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Models;
|
||||
|
||||
namespace Persistence.Repository.Repositories;
|
||||
|
||||
public class TimeSeriesDataCachedRepository<TEntity, TDto> : TimeSeriesDataRepository<TEntity, TDto>
|
||||
where TEntity : class, ITimestampedData, new()
|
||||
where TDto : class, ITimeSeriesAbstractDto, new()
|
||||
{
|
||||
public static TDto? FirstByDate { get; private set; }
|
||||
public static CyclicArray<TDto> LastData { get; } = new CyclicArray<TDto>(CacheItemsCount);
|
||||
|
||||
private const int CacheItemsCount = 3600;
|
||||
|
||||
public TimeSeriesDataCachedRepository(DbContext db) : base(db)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var firstDateItem = await base.GetFirstAsync(CancellationToken.None);
|
||||
if (firstDateItem == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FirstByDate = firstDateItem;
|
||||
|
||||
var dtos = await base.GetLastAsync(CacheItemsCount, CancellationToken.None);
|
||||
dtos = dtos.OrderBy(d => d.Date);
|
||||
LastData.AddRange(dtos);
|
||||
}).Wait();
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<TDto>> GetGtDate(DateTimeOffset dateBegin, CancellationToken token)
|
||||
{
|
||||
|
||||
if (LastData.Count() == 0 || LastData[0].Date > dateBegin)
|
||||
{
|
||||
var dtos = await base.GetGtDate(dateBegin, token);
|
||||
return dtos;
|
||||
}
|
||||
|
||||
var items = LastData
|
||||
.Where(i => i.Date >= dateBegin);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public override async Task<int> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
|
||||
{
|
||||
var result = await base.InsertRange(dtos, token);
|
||||
if (result > 0)
|
||||
{
|
||||
|
||||
dtos = dtos.OrderBy(x => x.Date);
|
||||
|
||||
FirstByDate = dtos.First();
|
||||
LastData.AddRange(dtos);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async Task<DatesRangeDto?> GetDatesRange(CancellationToken token)
|
||||
{
|
||||
if (FirstByDate == null)
|
||||
return null;
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
return new DatesRangeDto
|
||||
{
|
||||
From = FirstByDate.Date,
|
||||
To = LastData[^1].Date
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<TDto>> GetResampledData(
|
||||
DateTimeOffset dateBegin,
|
||||
double intervalSec = 600d,
|
||||
int approxPointsCount = 1024,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var dtos = LastData.Where(i => i.Date >= dateBegin);
|
||||
if (LastData.Count == 0 || LastData[0].Date > dateBegin)
|
||||
{
|
||||
dtos = await base.GetGtDate(dateBegin, token);
|
||||
}
|
||||
|
||||
var dateEnd = dateBegin.AddSeconds(intervalSec);
|
||||
dtos = dtos
|
||||
.Where(i => i.Date <= dateEnd);
|
||||
|
||||
var ratio = dtos.Count() / approxPointsCount;
|
||||
if (ratio > 1)
|
||||
dtos = dtos
|
||||
.Where((_, index) => index % ratio == 0);
|
||||
|
||||
return dtos;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
using Mapster;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Models;
|
||||
using Persistence.Repositories;
|
||||
using Persistence.Database.Model;
|
||||
using Persistence.Repository.Data;
|
||||
|
||||
namespace Persistence.Repository.Repositories;
|
||||
public abstract class TimeSeriesDataRepository<TEntity, TDto> : ITimeSeriesDataRepository<TDto>
|
||||
where TEntity : class
|
||||
public class TimeSeriesDataRepository<TEntity, TDto> : ITimeSeriesDataRepository<TDto>
|
||||
where TEntity : class, ITimestampedData, new()
|
||||
where TDto : class, ITimeSeriesAbstractDto, new()
|
||||
{
|
||||
private DbContext db;
|
||||
@ -17,28 +16,32 @@ public abstract class TimeSeriesDataRepository<TEntity, TDto> : ITimeSeriesDataR
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
protected virtual IQueryable<TEntity> GetQueryReadOnly() => db.Set<TEntity>();
|
||||
protected virtual IQueryable<TEntity> GetQueryReadOnly() => this.db.Set<TEntity>();
|
||||
|
||||
public async Task<IEnumerable<TDto>> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token)
|
||||
public virtual async Task<DatesRangeDto?> GetDatesRange(CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly();
|
||||
var minDate = await query.MinAsync(o => o.Date, token);
|
||||
var maxDate = await query.MaxAsync(o => o.Date, token);
|
||||
|
||||
return new DatesRangeDto
|
||||
{
|
||||
From = minDate,
|
||||
To = maxDate
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<TDto>> GetGtDate(DateTimeOffset date, CancellationToken token)
|
||||
{
|
||||
var query = this.db.Set<TEntity>().Where(e => e.Date > date);
|
||||
var entities = await query.ToArrayAsync(token);
|
||||
|
||||
var dtos = entities.Select(e => e.Adapt<TDto>());
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<TDto>> GetGtDate(DateTimeOffset date, CancellationToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<int> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
|
||||
public virtual async Task<int> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
|
||||
{
|
||||
var entities = dtos.Select(d => d.Adapt<TEntity>());
|
||||
|
||||
@ -47,4 +50,50 @@ public abstract class TimeSeriesDataRepository<TEntity, TDto> : ITimeSeriesDataR
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async Task<IEnumerable<TDto>> GetLastAsync(int takeCount, CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly()
|
||||
.OrderByDescending(e => e.Date)
|
||||
.Take(takeCount);
|
||||
|
||||
var entities = await query.ToArrayAsync(token);
|
||||
var dtos = entities.Select(e => e.Adapt<TDto>());
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
protected async Task<TDto?> GetFirstAsync(CancellationToken token)
|
||||
{
|
||||
var query = GetQueryReadOnly()
|
||||
.OrderBy(e => e.Date);
|
||||
|
||||
var entity = await query.FirstOrDefaultAsync(token);
|
||||
|
||||
if (entity == null)
|
||||
return null;
|
||||
|
||||
var dto = entity.Adapt<TDto>();
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async virtual Task<IEnumerable<TDto>> GetResampledData(
|
||||
DateTimeOffset dateBegin,
|
||||
double intervalSec = 600d,
|
||||
int approxPointsCount = 1024,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var dtos = await GetGtDate(dateBegin, token);
|
||||
|
||||
var dateEnd = dateBegin.AddSeconds(intervalSec);
|
||||
dtos = dtos
|
||||
.Where(i => i.Date <= dateEnd);
|
||||
|
||||
var ratio = dtos.Count() / approxPointsCount;
|
||||
if (ratio > 1)
|
||||
dtos = dtos
|
||||
.Where((_, index) => index % ratio == 0);
|
||||
|
||||
return dtos;
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Persistence.Database;
|
||||
using Persistence.Database.Model;
|
||||
|
||||
namespace Persistence.Repository;
|
||||
public class Startup
|
||||
{
|
||||
public static void BeforeRunHandler(IHost host)
|
||||
{
|
||||
using var scope = host.Services.CreateScope();
|
||||
var provider = scope.ServiceProvider;
|
||||
|
||||
var context = provider.GetRequiredService<DbContext>();
|
||||
context.Database.EnsureCreatedAndMigrated();
|
||||
|
||||
}
|
||||
}
|
@ -11,7 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Repository", "P
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Database", "Persistence.Database\Persistence.Database.csproj", "{F77475D1-D074-407A-9D69-2FADDDAE2056}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.IntegrationTests", "Persistence.IntegrationTests\Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.IntegrationTests", "Persistence.IntegrationTests\Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Database.Postgres", "Persistence.Database.Postgres\Persistence.Database.Postgres.csproj", "{CC284D27-162D-490C-B6CF-74D666B7C5F3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.Client", "Persistence.Client\Persistence.Client.csproj", "{84B68660-48E6-4974-A4E5-517552D9DE23}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -39,6 +43,14 @@ Global
|
||||
{10752C25-3773-4081-A1F2-215A1D950126}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{10752C25-3773-4081-A1F2-215A1D950126}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{10752C25-3773-4081-A1F2-215A1D950126}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CC284D27-162D-490C-B6CF-74D666B7C5F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CC284D27-162D-490C-B6CF-74D666B7C5F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CC284D27-162D-490C-B6CF-74D666B7C5F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CC284D27-162D-490C-B6CF-74D666B7C5F3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84B68660-48E6-4974-A4E5-517552D9DE23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84B68660-48E6-4974-A4E5-517552D9DE23}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84B68660-48E6-4974-A4E5-517552D9DE23}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84B68660-48E6-4974-A4E5-517552D9DE23}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -31,7 +31,7 @@ public interface IChangeLogApi<TDto, TChangeLogDto>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> AddAsync(TDto dto, CancellationToken token);
|
||||
Task<ActionResult<int>> Add(TDto dto, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Добавить несколько записей
|
||||
@ -39,7 +39,7 @@ public interface IChangeLogApi<TDto, TChangeLogDto>
|
||||
/// <param name="dtos"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> AddRangeAsync(IEnumerable<TDto> dtos, CancellationToken token);
|
||||
Task<ActionResult<int>> AddRange(IEnumerable<TDto> dtos, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Обновить одну запись
|
||||
@ -47,7 +47,7 @@ public interface IChangeLogApi<TDto, TChangeLogDto>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> UpdateAsync(TDto dto, CancellationToken token);
|
||||
Task<ActionResult<int>> Update(TDto dto, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Обновить несколько записей
|
||||
@ -55,7 +55,7 @@ public interface IChangeLogApi<TDto, TChangeLogDto>
|
||||
/// <param name="dtos"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> UpdateRangeAsync(IEnumerable<TDto> dtos, CancellationToken token);
|
||||
Task<ActionResult<int>> UpdateRange(IEnumerable<TDto> dtos, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Удалить одну запись
|
||||
@ -63,7 +63,7 @@ public interface IChangeLogApi<TDto, TChangeLogDto>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> DeleteAsync(int id, CancellationToken token);
|
||||
Task<ActionResult<int>> Delete(int id, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Удалить несколько записей
|
||||
@ -71,5 +71,5 @@ public interface IChangeLogApi<TDto, TChangeLogDto>
|
||||
/// <param name="ids"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> DeleteRangeAsync(IEnumerable<int> ids, CancellationToken token);
|
||||
Task<ActionResult<int>> DeleteRange(IEnumerable<int> ids, CancellationToken token);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ public interface IDictionaryElementApi<TDto> where TDto : class, new()
|
||||
/// <param name="dictionaryKey">ключ справочника</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<TDto>>> GetAsync(Guid dictionaryKey, CancellationToken token);
|
||||
Task<ActionResult<IEnumerable<TDto>>> Get(Guid dictionaryKey, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Добавить элемент в справочник
|
||||
@ -22,7 +22,7 @@ public interface IDictionaryElementApi<TDto> where TDto : class, new()
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<Guid>> AddAsync(Guid dictionaryKey, TDto dto, CancellationToken token);
|
||||
Task<ActionResult<Guid>> Add(Guid dictionaryKey, TDto dto, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Изменить одну запись
|
||||
@ -32,7 +32,7 @@ public interface IDictionaryElementApi<TDto> where TDto : class, new()
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<Guid>> UpdateAsync(Guid dictionaryKey, Guid dictionaryElementKey, TDto dto, CancellationToken token);
|
||||
Task<ActionResult<Guid>> Update(Guid dictionaryKey, Guid dictionaryElementKey, TDto dto, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Удалить одну запись
|
||||
@ -41,5 +41,5 @@ public interface IDictionaryElementApi<TDto> where TDto : class, new()
|
||||
/// <param name="dictionaryElementKey">ключ элемента в справочнике</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> DeleteAsync(Guid dictionaryKey, Guid dictionaryElementKey, CancellationToken token);
|
||||
Task<ActionResult<int>> Delete(Guid dictionaryKey, Guid dictionaryElementKey, CancellationToken token);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public interface ISetpointApi
|
||||
/// <param name="setpoitKeys">ключи уставок</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrentAsync(IEnumerable<Guid> setpoitKeys, CancellationToken token);
|
||||
Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent(IEnumerable<Guid> setpoitKeys, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить значения уставок за определенный момент времени
|
||||
@ -23,7 +23,7 @@ public interface ISetpointApi
|
||||
/// <param name="historyMoment">дата, на которую получаем данные</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistoryAsync(IEnumerable<Guid> setpoitKeys, DateTimeOffset historyMoment, CancellationToken token);
|
||||
Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistory(IEnumerable<Guid> setpoitKeys, DateTimeOffset historyMoment, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить историю изменений значений уставок
|
||||
@ -31,7 +31,7 @@ public interface ISetpointApi
|
||||
/// <param name="setpoitKeys">ключи уставок</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLogAsync(IEnumerable<Guid> setpoitKeys, CancellationToken token);
|
||||
Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog(IEnumerable<Guid> setpoitKeys, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Метод сохранения уставки
|
||||
@ -40,5 +40,5 @@ public interface ISetpointApi
|
||||
/// <param name="newValue">значение</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<int>> SaveAsync(Guid setpointKey, object newValue, CancellationToken token);
|
||||
Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ public interface ISyncApi<TDto> where TDto : class, new()
|
||||
/// <param name="take">количество записей</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<TDto>>> GetPartAsync(DateTimeOffset dateBegin, int take = 24 * 60 * 60, CancellationToken token = default);
|
||||
Task<ActionResult<IEnumerable<TDto>>> GetPart(DateTimeOffset dateBegin, int take = 24 * 60 * 60, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
|
@ -19,5 +19,5 @@ public interface ITableDataApi<TDto, TRequest>
|
||||
/// <param name="request">параметры фильтрации</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<PaginationContainer<TDto>>> GetPageAsync(TRequest request, CancellationToken token);
|
||||
Task<ActionResult<PaginationContainer<TDto>>> GetPage(TRequest request, CancellationToken token);
|
||||
}
|
||||
|
@ -4,23 +4,27 @@ using Persistence.Models;
|
||||
namespace Persistence.API;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс для работы с API графиков
|
||||
/// Базовый интерфейс для работы с временными рядами
|
||||
/// </summary>
|
||||
public interface IGraphDataApi<TDto>
|
||||
public interface ITimeSeriesBaseDataApi<TDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить список объектов с прореживанием, удовлетворящий диапазону дат
|
||||
/// Получить список объектов с прореживанием, удовлетворяющий диапазону дат
|
||||
/// </summary>
|
||||
/// <param name="dateBegin">дата начала</param>
|
||||
/// <param name="dateEnd">дата окончания</param>
|
||||
/// <param name="approxPointsCount"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<IEnumerable<TDto>>> GetThinnedDataAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024);
|
||||
Task<IActionResult> GetResampledData(
|
||||
DateTimeOffset dateBegin,
|
||||
double intervalSec = 600d,
|
||||
int approxPointsCount = 1024,
|
||||
CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token);
|
||||
Task<IActionResult> GetDatesRange(CancellationToken token);
|
||||
}
|
@ -11,24 +11,16 @@ namespace Persistence.API;
|
||||
/// <summary>
|
||||
/// Интерфейс для работы с API временных данных
|
||||
/// </summary>
|
||||
public interface ITimeSeriesDataApi<TDto>
|
||||
public interface ITimeSeriesDataApi<TDto> : ITimeSeriesBaseDataApi<TDto>
|
||||
where TDto : class, ITimeSeriesAbstractDto, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить список объектов, удовлетворяющий диапазон дат
|
||||
/// </summary>
|
||||
/// <param name="dateBegin">дата начала</param>
|
||||
/// <param name="dateEnd">дата окончания</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IActionResult> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IActionResult> GetDatesRangeAsync(CancellationToken token);
|
||||
Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Добавление записей
|
||||
@ -36,7 +28,7 @@ public interface ITimeSeriesDataApi<TDto>
|
||||
/// <param name="dtos"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IActionResult> InsertRangeAsync(IEnumerable<TDto> dtos, CancellationToken token);
|
||||
Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token);
|
||||
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public class ChangeLogDto<T> where T: class
|
||||
public DateTimeOffset Creation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Дата устаревания (например при удалении)
|
||||
/// Дата устаревания (например, при удалении)
|
||||
/// </summary>
|
||||
public DateTimeOffset? Obsolete { get; set; }
|
||||
|
||||
|
12
Persistence/Models/Configurations/AuthUser.cs
Normal file
12
Persistence/Models/Configurations/AuthUser.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Persistence.Models.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Настройки credentials для авторизации
|
||||
/// </summary>
|
||||
public class AuthUser
|
||||
{
|
||||
public required string Username { get; set; }
|
||||
public required string Password { get; set; }
|
||||
public required string ClientId { get; set; }
|
||||
public required string GrantType { get; set; }
|
||||
}
|
18
Persistence/Models/Configurations/JwtParams.cs
Normal file
18
Persistence/Models/Configurations/JwtParams.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Persistence.Models.Configurations
|
||||
{
|
||||
public static class JwtParams
|
||||
{
|
||||
private static readonly string KeyValue = "супер секретный ключ для шифрования";
|
||||
public static SymmetricSecurityKey SecurityKey
|
||||
{
|
||||
get { return new SymmetricSecurityKey(Encoding.ASCII.GetBytes(KeyValue)); }
|
||||
}
|
||||
|
||||
public static readonly string Issuer = "a";
|
||||
|
||||
public static readonly string Audience = "a";
|
||||
}
|
||||
}
|
10
Persistence/Models/Configurations/JwtToken.cs
Normal file
10
Persistence/Models/Configurations/JwtToken.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Persistence.Models.Configurations
|
||||
{
|
||||
public class JwtToken
|
||||
{
|
||||
[JsonPropertyName("access_token")]
|
||||
public required string AccessToken { get; set; }
|
||||
}
|
||||
}
|
@ -5,9 +5,19 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Диапазон дат
|
||||
/// </summary>
|
||||
public class DatesRangeDto
|
||||
{
|
||||
public DateTimeOffset dateBegin { get; set; }
|
||||
/// <summary>
|
||||
/// Дата начала диапазона
|
||||
/// </summary>
|
||||
public DateTimeOffset From { get; set; }
|
||||
|
||||
public DateTimeOffset dateEnd { get; set; }
|
||||
/// <summary>
|
||||
/// Дата окончания диапазона
|
||||
/// </summary>
|
||||
public DateTimeOffset To { get; set; }
|
||||
}
|
||||
|
@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Models;
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс, описывающий временные данные
|
||||
|
@ -1,10 +1,23 @@
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер для поддержки постраничного просмотра таблиц
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class RequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Кол-во записей пропущенных с начала таблицы в запросе от api
|
||||
/// </summary>
|
||||
public int Skip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Кол-во записей в запросе от api
|
||||
/// </summary>
|
||||
public int Take { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Настройки сортировки
|
||||
/// </summary>
|
||||
public string SortSettings { get; set; } = string.Empty;
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Модель для описания лога уставки
|
||||
/// </summary>
|
||||
public class SetpointLogDto : SetpointValueDto
|
||||
{
|
||||
public DateTimeOffset Edit { get; set; }
|
||||
/// <summary>
|
||||
/// Дата сохранения уставки
|
||||
/// </summary>
|
||||
public DateTimeOffset Created { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ключ пользователя
|
||||
/// </summary>
|
||||
public int IdUser { get; set; }
|
||||
}
|
||||
|
@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Модель для хранения значения уставки
|
||||
/// </summary>
|
||||
public class SetpointValueDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public object Value { get; set; }
|
||||
/// Идентификатор уставки
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public Guid Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Значение уставки
|
||||
/// </summary>
|
||||
public required object Value { get; set; }
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
namespace Persistence.Models;
|
||||
|
||||
namespace Persistence.Models;
|
||||
/// <summary>
|
||||
/// Модель, описывающая пользователя
|
||||
/// </summary>
|
||||
public class UserDto
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
|
@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,10 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Persistence.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Persistence.Repositories;
|
||||
|
||||
@ -13,23 +7,30 @@ namespace Persistence.Repositories;
|
||||
/// </summary>
|
||||
public interface ISetpointRepository
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Получить значения уставок за определенный момент времени
|
||||
/// </summary>
|
||||
/// <param name="setpoitKeys"></param>
|
||||
/// <param name="historyMoment">дата, на которую получаем данные</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<SetpointValueDto>> GetHistoryAsync(IEnumerable<Guid> setpoitKeys, DateTimeOffset historyMoment, CancellationToken token);
|
||||
/// <summary>
|
||||
/// Получить значения уставок по набору ключей
|
||||
/// </summary>
|
||||
/// <param name="setpointKeys"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить значения уставок за определенный момент времени
|
||||
/// </summary>
|
||||
/// <param name="setpointKeys"></param>
|
||||
/// <param name="historyMoment">дата, на которую получаем данные</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<SetpointValueDto>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить историю изменений значений уставок
|
||||
/// </summary>
|
||||
/// <param name="setpoitKeys"></param>
|
||||
/// <param name="setpointKeys"></param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLogAsync(IEnumerable<Guid> setpoitKeys, CancellationToken token);
|
||||
Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Метод сохранения уставки
|
||||
@ -41,5 +42,5 @@ public interface ISetpointRepository
|
||||
/// <returns></returns>
|
||||
/// to do
|
||||
/// id User учесть в соответствующем методе репозитория
|
||||
Task<int> SaveAsync(Guid setpointKey, int idUser, object newValue, CancellationToken token);
|
||||
Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token);
|
||||
}
|
||||
|
@ -15,5 +15,5 @@ public interface ITableDataRepository<TDto, TRequest>
|
||||
/// <param name="request">параметры фильтрации</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<TDto>> GetAsync(TRequest request, CancellationToken token);
|
||||
Task<IEnumerable<TDto>> Get(TRequest request, CancellationToken token);
|
||||
}
|
||||
|
@ -5,22 +5,25 @@ namespace Persistence.Repositories;
|
||||
/// <summary>
|
||||
/// Интерфейс по работе с прореженными данными
|
||||
/// </summary>
|
||||
public interface IGraphDataRepository<TDto>
|
||||
public interface ITimeSeriesBaseRepository<TDto>
|
||||
where TDto : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить список объектов с прореживанием
|
||||
/// </summary>
|
||||
/// <param name="dateBegin">дата начала</param>
|
||||
/// <param name="dateEnd">дата окончания</param>
|
||||
/// <param name="approxPointsCount"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<TDto>> GetThinnedDataAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, int approxPointsCount = 1024);
|
||||
Task<IEnumerable<TDto>> GetResampledData(
|
||||
DateTimeOffset dateBegin,
|
||||
double intervalSec = 600d,
|
||||
int approxPointsCount = 1024,
|
||||
CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token);
|
||||
Task<DatesRangeDto?> GetDatesRange(CancellationToken token);
|
||||
}
|
@ -6,24 +6,9 @@ namespace Persistence.Repositories;
|
||||
/// Интерфейс по работе с временными данными
|
||||
/// </summary>
|
||||
/// <typeparam name="TDto"></typeparam>
|
||||
public interface ITimeSeriesDataRepository<TDto> : ISyncRepository<TDto>
|
||||
public interface ITimeSeriesDataRepository<TDto> : ISyncRepository<TDto>, ITimeSeriesBaseRepository<TDto>
|
||||
where TDto : class, ITimeSeriesAbstractDto, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Получить страницу списка объектов
|
||||
/// </summary>
|
||||
/// <param name="dateBegin">дата начала</param>
|
||||
/// <param name="dateEnd">дата окончания</param>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<TDto>> GetAsync(DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Получить диапазон дат, для которых есть данные в репозитории
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token);
|
||||
/// <summary>
|
||||
/// Добавление записей
|
||||
/// </summary>
|
||||
|
@ -1,12 +1,12 @@
|
||||
namespace Persistence.Services;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Сервис по работе с БД
|
||||
/// </summary>
|
||||
internal interface IArchiveService
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// Переименование БД
|
||||
/// </summary>
|
||||
/// <param name="connectionString"></param>
|
||||
/// <param name="databaseName"></param>
|
||||
@ -15,7 +15,7 @@ internal interface IArchiveService
|
||||
Task RenameDatabase(string connectionString, string databaseName, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// Создание БД
|
||||
/// </summary>
|
||||
/// <param name="connectionString"></param>
|
||||
/// <param name="databaseName"></param>
|
||||
|
@ -1,4 +1,5 @@
|
||||
namespace Persistence.Services;
|
||||
|
||||
public interface ITimeSeriesDataObserverService
|
||||
{
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user