diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs new file mode 100644 index 0000000..9a6bd61 --- /dev/null +++ b/Persistence.API/Controllers/SetpointController.cs @@ -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>> GetCurrent([FromQuery] IEnumerable setpointKeys, CancellationToken token) + { + var result = await setpointRepository.GetCurrent(setpointKeys, token); + + return Ok(result); + } + + [HttpGet("history")] + public async Task>> GetHistory([FromQuery] IEnumerable setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token) + { + var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token); + + return Ok(result); + } + + [HttpGet("log")] + public async Task>>> GetLog([FromQuery] IEnumerable setpointKeys, CancellationToken token) + { + var result = await setpointRepository.GetLog(setpointKeys, token); + + return Ok(result); + } + + [HttpPost] + public async Task> Save(Guid setpointKey, object newValue, CancellationToken token) + { + // ToDo: вычитка idUser + await setpointRepository.Save(setpointKey, newValue, 0, token); + + return Ok(); + } + } +} diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs index e91e3a0..cdfca4c 100644 --- a/Persistence.API/DependencyInjection.cs +++ b/Persistence.API/DependencyInjection.cs @@ -1,8 +1,10 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; -using System.Text.Json.Nodes; +using Persistence.Models.Configurations; +using Swashbuckle.AspNetCore.SwaggerGen; namespace Persistence.API; @@ -30,59 +32,164 @@ public static class DependencyInjection }); c.SwaggerDoc("v1", new OpenApiInfo { Title = "Persistence web api", Version = "v1" }); - c.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"]), - } - } - }); - c.AddSecurityRequirement(new OpenApiSecurityRequirement() - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Keycloack" - }, - Scheme = "Bearer", - Name = "Bearer", - In = ParameterLocation.Header, - }, - new List() - } - }); + var needUseKeyCloak = configuration.GetSection("NeedUseKeyCloak").Get(); + 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; - //c.IncludeXmlComments(xmlPath, includeControllerXmlComment); - //c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment); - }); + //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + //var includeControllerXmlComment = true; + //options.IncludeXmlComments(xmlPath, includeControllerXmlComment); + //options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment); + }); } - public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration) + #region Authentication + public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration) { - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(o => - { - o.RequireHttpsMetadata = false; - o.Audience = configuration["Authentication:Audience"]; - o.MetadataAddress = configuration["Authentication:MetadataAddress"]!; - o.TokenValidationParameters = new TokenValidationParameters - { - ValidIssuer = configuration["Authentication:ValidIssuer"], - }; - }); - } + var needUseKeyCloak = configuration + .GetSection("NeedUseKeyCloak") + .Get(); + 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()!; + + 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() + } + }); + } + + 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() + } + }); + } + #endregion } diff --git a/Persistence.API/Properties/launchSettings.json b/Persistence.API/Properties/launchSettings.json index c2ccc25..52a969d 100644 --- a/Persistence.API/Properties/launchSettings.json +++ b/Persistence.API/Properties/launchSettings.json @@ -8,7 +8,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5032" + "applicationUrl": "http://localhost:13616" }, "IIS Express": { "commandName": "IISExpress", diff --git a/Persistence.API/appsettings.Development.json b/Persistence.API/appsettings.Development.json index 0c208ae..896c0b7 100644 --- a/Persistence.API/appsettings.Development.json +++ b/Persistence.API/appsettings.Development.json @@ -4,5 +4,6 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "NeedUseKeyCloak": false } diff --git a/Persistence.API/appsettings.Tests.json b/Persistence.API/appsettings.Tests.json index 033464f..6201a8f 100644 --- a/Persistence.API/appsettings.Tests.json +++ b/Persistence.API/appsettings.Tests.json @@ -1,11 +1,12 @@ -{ +{ "DbConnection": { "Host": "localhost", "Port": 5432, "Username": "postgres", "Password": "q" }, - "KeycloakTestUser": { + "NeedUseKeyCloak": false, + "AuthUser": { "username": "myuser", "password": 12345, "clientId": "webapi", diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs new file mode 100644 index 0000000..49733f0 --- /dev/null +++ b/Persistence.Client/Clients/ISetpointClient.cs @@ -0,0 +1,24 @@ +using Persistence.Models; +using Refit; + +namespace Persistence.Client.Clients; + +/// +/// Интерфейс для тестирования API, предназначенного для работы с уставками +/// +public interface ISetpointClient +{ + private const string BaseRoute = "/api/setpoint"; + + [Get($"{BaseRoute}/current")] + Task>> GetCurrent([Query(CollectionFormat.Multi)] IEnumerable setpointKeys); + + [Get($"{BaseRoute}/history")] + Task>> GetHistory([Query(CollectionFormat.Multi)] IEnumerable setpointKeys, [Query] DateTimeOffset historyMoment); + + [Get($"{BaseRoute}/log")] + Task>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable setpointKeys); + + [Post($"{BaseRoute}/")] + Task Save(Guid setpointKey, object newValue); +} diff --git a/Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs b/Persistence.Client/Clients/ITimeSeriesClient.cs similarity index 93% rename from Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs rename to Persistence.Client/Clients/ITimeSeriesClient.cs index 4030d25..8f7ef0e 100644 --- a/Persistence.IntegrationTests/Clients/ITimeSeriesClient.cs +++ b/Persistence.Client/Clients/ITimeSeriesClient.cs @@ -2,7 +2,7 @@ using Persistence.Models; using Refit; -namespace Persistence.IntegrationTests.Clients; +namespace Persistence.Client.Clients; public interface ITimeSeriesClient where TDto : class, new() { diff --git a/Persistence.IntegrationTests/Clients/ITimestampedSetClient.cs b/Persistence.Client/Clients/ITimestampedSetClient.cs similarity index 100% rename from Persistence.IntegrationTests/Clients/ITimestampedSetClient.cs rename to Persistence.Client/Clients/ITimestampedSetClient.cs diff --git a/Persistence.Client/Helpers/ApiTokenHelper.cs b/Persistence.Client/Helpers/ApiTokenHelper.cs new file mode 100644 index 0000000..e508922 --- /dev/null +++ b/Persistence.Client/Helpers/ApiTokenHelper.cs @@ -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()!; + var needUseKeyCloak = configuration + .GetSection("NeedUseKeyCloak") + .Get()!; + var keycloakGetTokenUrl = configuration.GetSection("KeycloakGetTokenUrl").Get() ?? 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() + { + 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(keyCloackResponse.Content)!; + return token.AccessToken; + } + + return String.Empty; + } +} diff --git a/Persistence.Client/Persistence.Client.csproj b/Persistence.Client/Persistence.Client.csproj new file mode 100644 index 0000000..d699df7 --- /dev/null +++ b/Persistence.Client/Persistence.Client.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/Persistence.Client/PersistenceClientFactory.cs b/Persistence.Client/PersistenceClientFactory.cs new file mode 100644 index 0000000..e84327d --- /dev/null +++ b/Persistence.Client/PersistenceClientFactory.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using Microsoft.Extensions.Configuration; +using Persistence.Client.Helpers; +using Refit; + +namespace Persistence.Client +{ + /// + /// Фабрика клиентов для доступа к Persistence - сервису + /// + 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() + { + return RestService.For(httpClient, RefitSettings); + } + } +} diff --git a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs new file mode 100644 index 0000000..9399dd4 --- /dev/null +++ b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.Designer.cs @@ -0,0 +1,146 @@ +// +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 + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AxialLoad") + .HasColumnType("double precision") + .HasColumnName("axialLoad"); + + b.Property("BitDepth") + .HasColumnType("double precision") + .HasColumnName("bitDepth"); + + b.Property("BlockPosition") + .HasColumnType("double precision") + .HasColumnName("blockPosition"); + + b.Property("BlockSpeed") + .HasColumnType("double precision") + .HasColumnName("blockSpeed"); + + b.Property("Flow") + .HasColumnType("double precision") + .HasColumnName("flow"); + + b.Property("HookWeight") + .HasColumnType("double precision") + .HasColumnName("hookWeight"); + + b.Property("IdFeedRegulator") + .HasColumnType("integer") + .HasColumnName("idFeedRegulator"); + + b.Property("Mode") + .HasColumnType("integer") + .HasColumnName("mode"); + + b.Property("Mse") + .HasColumnType("double precision") + .HasColumnName("mse"); + + b.Property("MseState") + .HasColumnType("smallint") + .HasColumnName("mseState"); + + b.Property("Pressure") + .HasColumnType("double precision") + .HasColumnName("pressure"); + + b.Property("Pump0Flow") + .HasColumnType("double precision") + .HasColumnName("pump0Flow"); + + b.Property("Pump1Flow") + .HasColumnType("double precision") + .HasColumnName("pump1Flow"); + + b.Property("Pump2Flow") + .HasColumnType("double precision") + .HasColumnName("pump2Flow"); + + b.Property("RotorSpeed") + .HasColumnType("double precision") + .HasColumnName("rotorSpeed"); + + b.Property("RotorTorque") + .HasColumnType("double precision") + .HasColumnName("rotorTorque"); + + b.Property("TimeStamp") + .HasColumnType("integer") + .HasColumnName("timestamp"); + + b.Property("User") + .HasColumnType("text") + .HasColumnName("user"); + + b.Property("WellDepth") + .HasColumnType("double precision") + .HasColumnName("wellDepth"); + + b.HasKey("Id"); + + b.ToTable("DataSaub"); + }); + + modelBuilder.Entity("Persistence.Database.Model.Setpoint", b => + { + b.Property("Key") + .HasColumnType("uuid") + .HasComment("Ключ"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasComment("Дата изменения уставки"); + + b.Property("IdUser") + .HasColumnType("integer") + .HasComment("Id автора последнего изменения"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Значение уставки"); + + b.HasKey("Key", "Created"); + + b.ToTable("Setpoint"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs new file mode 100644 index 0000000..49e438a --- /dev/null +++ b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Persistence.Database.Postgres.Migrations +{ + /// + public partial class SetpointMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Setpoint", + columns: table => new + { + Key = table.Column(type: "uuid", nullable: false, comment: "Ключ"), + Created = table.Column(type: "timestamp with time zone", nullable: false, comment: "Дата изменения уставки"), + Value = table.Column(type: "jsonb", nullable: false, comment: "Значение уставки"), + IdUser = table.Column(type: "integer", nullable: false, comment: "Id автора последнего изменения") + }, + constraints: table => + { + table.PrimaryKey("PK_Setpoint", x => new { x.Key, x.Created }); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Setpoint"); + } + } +} diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs index 394c112..f41f669 100644 --- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs +++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs @@ -106,6 +106,30 @@ namespace Persistence.Database.Postgres.Migrations b.ToTable("DataSaub"); }); + + modelBuilder.Entity("Persistence.Database.Model.Setpoint", b => + { + b.Property("Key") + .HasColumnType("uuid") + .HasComment("Ключ"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasComment("Дата изменения уставки"); + + b.Property("IdUser") + .HasColumnType("integer") + .HasComment("Id автора последнего изменения"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb") + .HasComment("Значение уставки"); + + b.HasKey("Key", "Created"); + + b.ToTable("Setpoint"); + }); #pragma warning restore 612, 618 } } diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs index 22462ed..053df12 100644 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ b/Persistence.Database.Postgres/PersistenceDbContext.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Npgsql; using Persistence.Database.Entity; using System.Data.Common; @@ -8,6 +8,8 @@ public partial class PersistenceDbContext : DbContext, IPersistenceDbContext { public DbSet DataSaub => Set(); + public DbSet Setpoint => Set(); + public DbSet TimestampedSets => Set(); public PersistenceDbContext() diff --git a/Persistence.Database/Entity/Setpoint.cs b/Persistence.Database/Entity/Setpoint.cs new file mode 100644 index 0000000..ef6b5dc --- /dev/null +++ b/Persistence.Database/Entity/Setpoint.cs @@ -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; } + } +} diff --git a/Persistence.Database/Model/IPersistenceDbContext.cs b/Persistence.Database/Model/IPersistenceDbContext.cs new file mode 100644 index 0000000..2c1aebb --- /dev/null +++ b/Persistence.Database/Model/IPersistenceDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Persistence.Database.Model; +public interface IPersistenceDbContext : IDisposable +{ + DbSet DataSaub { get; } + DbSet Setpoint { get; } + DatabaseFacade Database { get; } + Task SaveChangesAsync(CancellationToken cancellationToken); +} diff --git a/Persistence.IntegrationTests/ApiTokenHelper.cs b/Persistence.IntegrationTests/ApiTokenHelper.cs deleted file mode 100644 index 3c1fda2..0000000 --- a/Persistence.IntegrationTests/ApiTokenHelper.cs +++ /dev/null @@ -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 roles) - //{ - // var claims = new List - // { - // 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); - //} -} diff --git a/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs b/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs index ae19a8a..9febbb5 100644 --- a/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/DataSaubControllerTest.cs @@ -1,4 +1,5 @@ -using Persistence.Database.Model; +using Persistence.Client; +using Persistence.Database.Model; using Persistence.Repository.Data; using Xunit; diff --git a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs new file mode 100644 index 0000000..faa0147 --- /dev/null +++ b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs @@ -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(); + + setpointClient = persistenceClientFactory.GetClient(); + } + + [Fact] + public async Task GetCurrent_returns_success() + { + //arrange + var setpointKeys = new List() + { + 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.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.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 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; + } + } +} diff --git a/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs index a96826d..87efa43 100644 --- a/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs @@ -1,7 +1,9 @@ -using Mapster; -using Persistence.Database.Model; -using Persistence.IntegrationTests.Clients; using System.Net; +using Mapster; +using Microsoft.Extensions.DependencyInjection; +using Persistence.Client; +using Persistence.Client.Clients; +using Persistence.Database.Model; using Xunit; namespace Persistence.IntegrationTests.Controllers; @@ -9,16 +11,17 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat where TEntity : class, ITimestampedData, new() where TDto : class, new() { - private ITimeSeriesClient client; + private ITimeSeriesClient timeSeriesClient; public TimeSeriesBaseControllerTest(WebAppFactoryFixture factory) : base(factory) { dbContext.CleanupDbSet(); - Task.Run(async () => - { - client = await factory.GetAuthorizedHttpClient>(string.Empty); - }).Wait(); + var scope = factory.Services.CreateScope(); + var persistenceClientFactory = scope.ServiceProvider + .GetRequiredService(); + + timeSeriesClient = persistenceClientFactory.GetClient>(); } public async Task InsertRangeSuccess(TDto dto) @@ -27,7 +30,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat var expected = dto.Adapt(); //act - var response = await client.InsertRange(new TDto[] { expected }); + var response = await timeSeriesClient.InsertRange(new TDto[] { expected }); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -43,7 +46,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat dbContext.SaveChanges(); - var response = await client.Get(beginDate, endDate); + var response = await timeSeriesClient.Get(beginDate, endDate); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -65,7 +68,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat dbContext.SaveChanges(); - var response = await client.GetDatesRange(); + var response = await timeSeriesClient.GetDatesRange(); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -95,7 +98,7 @@ public abstract class TimeSeriesBaseControllerTest : BaseIntegrat dbContext.SaveChanges(); - var response = await client.GetResampledData(entity.Date.AddMinutes(-1), differenceBetweenStartAndEndDays * 24 * 60 * 60 + 60, approxPointsCount); + var response = await timeSeriesClient.GetResampledData(entity.Date.AddMinutes(-1), differenceBetweenStartAndEndDays * 24 * 60 * 60 + 60, approxPointsCount); //assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/Persistence.IntegrationTests/JwtToken.cs b/Persistence.IntegrationTests/JwtToken.cs deleted file mode 100644 index 38bf315..0000000 --- a/Persistence.IntegrationTests/JwtToken.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Persistence.IntegrationTests; -public class JwtToken -{ - [JsonPropertyName("access_token")] - public required string AccessToken { get; set; } -} diff --git a/Persistence.IntegrationTests/KeyCloakUser.cs b/Persistence.IntegrationTests/KeyCloakUser.cs deleted file mode 100644 index aa4d335..0000000 --- a/Persistence.IntegrationTests/KeyCloakUser.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Persistence.IntegrationTests; - -/// -/// настройки credentials для пользователя в KeyCloak -/// -public class KeyCloakUser -{ - /// - /// - /// - public required string Username { get; set; } - - /// - /// - /// - public required string Password { get; set; } - - /// - /// - /// - public required string ClientId { get; set; } - - /// - /// - /// - public required string GrantType { get; set; } -} diff --git a/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj b/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj index f36e78d..a5cf35f 100644 --- a/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj +++ b/Persistence.IntegrationTests/Persistence.IntegrationTests.csproj @@ -25,6 +25,7 @@ + diff --git a/Persistence.IntegrationTests/TestHttpClientFactory.cs b/Persistence.IntegrationTests/TestHttpClientFactory.cs new file mode 100644 index 0000000..287498d --- /dev/null +++ b/Persistence.IntegrationTests/TestHttpClientFactory.cs @@ -0,0 +1,19 @@ +namespace Persistence.IntegrationTests +{ + /// + /// Фабрика HTTP клиентов для интеграционных тестов + /// + public class TestHttpClientFactory : IHttpClientFactory + { + private readonly WebAppFactoryFixture factory; + + public TestHttpClientFactory(WebAppFactoryFixture factory) + { + this.factory = factory; + } + public HttpClient CreateClient(string name) + { + return factory.CreateClient(); + } + } +} diff --git a/Persistence.IntegrationTests/WebAppFactoryFixture.cs b/Persistence.IntegrationTests/WebAppFactoryFixture.cs index 740d3c5..1d99a2a 100644 --- a/Persistence.IntegrationTests/WebAppFactoryFixture.cs +++ b/Persistence.IntegrationTests/WebAppFactoryFixture.cs @@ -1,70 +1,59 @@ -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 Microsoft.Extensions.DependencyInjection.Extensions; using Persistence.API; using Persistence.Database; +using Persistence.Client; using Persistence.Database.Model; using Persistence.Database.Postgres; -using Refit; using RestSharp; -using System.Net.Http.Headers; -using System.Text.Json; namespace Persistence.IntegrationTests; public class WebAppFactoryFixture : WebApplicationFactory { - 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; - private readonly KeyCloakUser keycloakTestUser; - public readonly string KeycloakGetTokenUrl; - - public WebAppFactoryFixture() - { - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.Tests.json") - .Build(); - - var dbConnection = configuration.GetSection("DbConnection").Get()!; - connectionString = dbConnection.GetConnectionString(); - - keycloakTestUser = configuration.GetSection("KeycloakTestUser").Get()!; - - KeycloakGetTokenUrl = configuration.GetSection("KeycloakGetTokenUrl").Value!; - } + 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()!; + connectionString = dbConnection.GetConnectionString(); + }); + + builder.ConfigureServices(services => { var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); - if (descriptor != null) services.Remove(descriptor); - - services.AddDbContext(options => + services.AddDbContext(options => options.UseNpgsql(connectionString)); + services.RemoveAll(); + services.AddSingleton(provider => + { + return new TestHttpClientFactory(this); + }); + + services.AddSingleton(); + + var serviceProvider = services.BuildServiceProvider(); services.AddScoped(provider => provider.GetRequiredService()); var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); var scopedServices = scope.ServiceProvider; - var dbContext = scopedServices.GetRequiredService(); + var dbContext = scopedServices.GetRequiredService(); dbContext.Database.EnsureCreatedAndMigrated(); dbContext.SaveChanges(); - }); + }); } public override async ValueTask DisposeAsync() @@ -76,57 +65,4 @@ public class WebAppFactoryFixture : WebApplicationFactory await dbContext.Database.EnsureDeletedAsync(); } - - public T GetHttpClient(string uriSuffix) - { - var httpClient = CreateClient(); - if (string.IsNullOrEmpty(uriSuffix)) - return RestService.For(httpClient, RefitSettings); - - if (httpClient.BaseAddress is not null) - httpClient.BaseAddress = new Uri(httpClient.BaseAddress, uriSuffix); - - return RestService.For(httpClient, RefitSettings); - } - - public async Task GetAuthorizedHttpClient(string uriSuffix) - { - var httpClient = await GetAuthorizedHttpClient(); - if (string.IsNullOrEmpty(uriSuffix)) - return RestService.For(httpClient, RefitSettings); - - if (httpClient.BaseAddress is not null) - httpClient.BaseAddress = new Uri(httpClient.BaseAddress, uriSuffix); - - return RestService.For(httpClient, RefitSettings); - } - - private async Task GetAuthorizedHttpClient() - { - var httpClient = CreateClient(); - var token = await GetTokenAsync(); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - return httpClient; - } - - private async Task GetTokenAsync() - { - var restClient = new RestClient(); - - var request = new RestRequest(KeycloakGetTokenUrl, Method.Post); - request.AddParameter("username", keycloakTestUser.Username); - request.AddParameter("password", keycloakTestUser.Password); - request.AddParameter("client_id", keycloakTestUser.ClientId); - request.AddParameter("grant_type", keycloakTestUser.GrantType); - - var keyCloackResponse = await restClient.PostAsync(request); - if (keyCloackResponse.IsSuccessful && !String.IsNullOrEmpty(keyCloackResponse.Content)) - { - var token = JsonSerializer.Deserialize(keyCloackResponse.Content)!; - return token.AccessToken; - } - - return String.Empty; - } } diff --git a/Persistence.Repository/Data/SetpointDto.cs b/Persistence.Repository/Data/SetpointDto.cs new file mode 100644 index 0000000..4a20aa4 --- /dev/null +++ b/Persistence.Repository/Data/SetpointDto.cs @@ -0,0 +1,28 @@ +namespace Persistence.Repository.Data +{ + /// + /// Модель для работы с уставкой + /// + public class SetpointDto + { + /// + /// Идентификатор уставки + /// + public int Id { get; set; } + + /// + /// Значение уставки + /// + public required object Value { get; set; } + + /// + /// Дата сохранения уставки + /// + public DateTimeOffset Edit { get; set; } + + /// + /// Ключ пользователя + /// + public int IdUser { get; set; } + } +} diff --git a/Persistence.Repository/DependencyInjection.cs b/Persistence.Repository/DependencyInjection.cs index 3a27ed1..e79c555 100644 --- a/Persistence.Repository/DependencyInjection.cs +++ b/Persistence.Repository/DependencyInjection.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Persistence.Database.Model; using Persistence.Repositories; using Persistence.Repository.Data; @@ -15,6 +15,8 @@ public static class DependencyInjection { MapsterSetup(); + services.AddTransient, TimeSeriesDataRepository>(); + services.AddTransient(); services.AddTransient, TimeSeriesDataCachedRepository>(); services.AddTransient(); diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs new file mode 100644 index 0000000..7f81c29 --- /dev/null +++ b/Persistence.Repository/Repositories/SetpointRepository.cs @@ -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 GetQueryReadOnly() => db.Set(); + + public async Task> GetCurrent(IEnumerable 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()); + + return dtos; + } + + public async Task> GetHistory(IEnumerable 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()); + + return dtos; + } + + public async Task>> GetLog(IEnumerable 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())); + + 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().AddAsync(entity, token); + await db.SaveChangesAsync(token); + } + } +} diff --git a/Persistence.sln b/Persistence.sln index ce44190..a8115a8 100644 --- a/Persistence.sln +++ b/Persistence.sln @@ -13,7 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Database", "Per EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.IntegrationTests", "Persistence.IntegrationTests\Persistence.IntegrationTests.csproj", "{10752C25-3773-4081-A1F2-215A1D950126}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.Database.Postgres", "Persistence.Database.Postgres\Persistence.Database.Postgres.csproj", "{CC284D27-162D-490C-B6CF-74D666B7C5F3}" +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 @@ -45,6 +47,10 @@ Global {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 diff --git a/Persistence/Models/Configurations/AuthUser.cs b/Persistence/Models/Configurations/AuthUser.cs new file mode 100644 index 0000000..86f11c5 --- /dev/null +++ b/Persistence/Models/Configurations/AuthUser.cs @@ -0,0 +1,12 @@ +namespace Persistence.Models.Configurations; + +/// +/// Настройки credentials для авторизации +/// +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; } +} diff --git a/Persistence/Models/Configurations/JwtParams.cs b/Persistence/Models/Configurations/JwtParams.cs new file mode 100644 index 0000000..d8b7b72 --- /dev/null +++ b/Persistence/Models/Configurations/JwtParams.cs @@ -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"; + } +} diff --git a/Persistence/Models/Configurations/JwtToken.cs b/Persistence/Models/Configurations/JwtToken.cs new file mode 100644 index 0000000..f787cc3 --- /dev/null +++ b/Persistence/Models/Configurations/JwtToken.cs @@ -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; } + } +} diff --git a/Persistence/Models/SetpointLogDto.cs b/Persistence/Models/SetpointLogDto.cs index 34b74f5..484be7a 100644 --- a/Persistence/Models/SetpointLogDto.cs +++ b/Persistence/Models/SetpointLogDto.cs @@ -1,4 +1,4 @@ -namespace Persistence.Models; +namespace Persistence.Models; /// /// Модель для описания лога уставки @@ -8,8 +8,8 @@ public class SetpointLogDto : SetpointValueDto /// /// Дата сохранения уставки /// - public DateTimeOffset DateEdit { get; set; } - + public DateTimeOffset Created { get; set; } + /// /// Ключ пользователя /// diff --git a/Persistence/Models/SetpointValueDto.cs b/Persistence/Models/SetpointValueDto.cs index 5c6d337..06a2b3e 100644 --- a/Persistence/Models/SetpointValueDto.cs +++ b/Persistence/Models/SetpointValueDto.cs @@ -1,18 +1,18 @@ -namespace Persistence.Models; +namespace Persistence.Models; /// /// Модель для хранения значения уставки /// public class SetpointValueDto { - /// /// Идентификатор уставки + /// /// - public int Id { get; set; } - + public Guid Key { get; set; } + /// /// Значение уставки /// - public object Value { get; set; } + public required object Value { get; set; } } diff --git a/Persistence/Persistence.csproj b/Persistence/Persistence.csproj index aaccee7..c857356 100644 --- a/Persistence/Persistence.csproj +++ b/Persistence/Persistence.csproj @@ -9,6 +9,7 @@ + diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs index 1e1e2c3..1d82b16 100644 --- a/Persistence/Repositories/ISetpointRepository.cs +++ b/Persistence/Repositories/ISetpointRepository.cs @@ -1,4 +1,4 @@ -using Persistence.Models; +using Persistence.Models; namespace Persistence.Repositories; @@ -7,23 +7,30 @@ namespace Persistence.Repositories; /// public interface ISetpointRepository { + /// + /// Получить значения уставок по набору ключей + /// + /// + /// + /// + Task> GetCurrent(IEnumerable setpointKeys, CancellationToken token); - /// - /// Получить значения уставок за определенный момент времени - /// - /// - /// дата, на которую получаем данные - /// - /// - Task> GetHistory(IEnumerable setpoitKeys, DateTimeOffset historyMoment, CancellationToken token); + /// + /// Получить значения уставок за определенный момент времени + /// + /// + /// дата, на которую получаем данные + /// + /// + Task> GetHistory(IEnumerable setpointKeys, DateTimeOffset historyMoment, CancellationToken token); /// /// Получить историю изменений значений уставок /// - /// + /// /// /// - Task>> GetLog(IEnumerable setpoitKeys, CancellationToken token); + Task>> GetLog(IEnumerable setpointKeys, CancellationToken token); /// /// Метод сохранения уставки @@ -35,5 +42,5 @@ public interface ISetpointRepository /// /// to do /// id User учесть в соответствующем методе репозитория - Task Save(Guid setpointKey, int idUser, object newValue, CancellationToken token); + Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token); }