diff --git a/Persistence.API/Controllers/ChangeLogController.cs b/Persistence.API/Controllers/ChangeLogController.cs index 63adaea..bc66a16 100644 --- a/Persistence.API/Controllers/ChangeLogController.cs +++ b/Persistence.API/Controllers/ChangeLogController.cs @@ -29,24 +29,25 @@ public class ChangeLogController : ControllerBase, IChangeLogApi var userId = User.GetUserId(); var result = await repository.InsertRange(userId, idDiscriminator, [dto], token); - return Ok(result); + return CreatedAtAction(nameof(Add), result); } [HttpPost("range")] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] public async Task AddRange( Guid idDiscriminator, - [FromBody] IEnumerable dtos, CancellationToken token) + [FromBody] IEnumerable dtos, + CancellationToken token = default) { var userId = User.GetUserId(); var result = await repository.InsertRange(userId, idDiscriminator, dtos, token); - return Ok(result); + return CreatedAtAction(nameof(AddRange), result); } [HttpDelete] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] - public async Task Delete(Guid id, CancellationToken token) + public async Task Delete(Guid id, CancellationToken token = default) { var userId = User.GetUserId(); var result = await repository.MarkAsDeleted(userId, [id], token); @@ -56,7 +57,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi [HttpDelete("range")] [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)] - public async Task DeleteRange(IEnumerable ids, CancellationToken token) + public async Task DeleteRange(IEnumerable ids, CancellationToken token = default) { var userId = User.GetUserId(); var result = await repository.MarkAsDeleted(userId, ids, token); @@ -69,7 +70,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi public async Task ClearAndInsertRange( Guid idDiscriminator, IEnumerable dtos, - CancellationToken token) + CancellationToken token = default) { var userId = User.GetUserId(); var result = await repository.ClearAndInsertRange(userId, idDiscriminator, dtos, token); @@ -81,7 +82,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi public async Task Update( Guid idDiscriminator, DataWithWellDepthAndSectionDto dto, - CancellationToken token) + CancellationToken token = default) { var userId = User.GetUserId(); var result = await repository.UpdateRange(userId, idDiscriminator, [dto], token); @@ -94,7 +95,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi public async Task UpdateRange( Guid idDiscriminator, IEnumerable dtos, - CancellationToken token) + CancellationToken token = default) { var userId = User.GetUserId(); var result = await repository.UpdateRange(userId, idDiscriminator, dtos, token); @@ -107,7 +108,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi public async Task GetCurrent( Guid idDiscriminator, [FromQuery]SectionPartRequest request, - CancellationToken token) + CancellationToken token = default) { var moment = new DateTimeOffset(3000, 1, 1, 0, 0, 0, TimeSpan.Zero); var result = await repository.GetByDate(idDiscriminator, moment, request, token); @@ -121,7 +122,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi Guid idDiscriminator, DateTimeOffset moment, [FromQuery] SectionPartRequest request, - CancellationToken token) + CancellationToken token = default) { var result = await repository.GetByDate(idDiscriminator, moment, request, token); @@ -130,7 +131,11 @@ public class ChangeLogController : ControllerBase, IChangeLogApi [HttpGet("history")] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task GetChangeLogForDate(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) + public async Task GetChangeLogForDate( + Guid idDiscriminator, + DateTimeOffset dateBegin, + DateTimeOffset dateEnd, + CancellationToken token = default) { var result = await repository.GetChangeLogForDate(idDiscriminator, dateBegin, dateEnd, token); @@ -139,7 +144,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi [HttpGet("datesChange")] [ProducesResponseType(typeof(IEnumerable), (int)HttpStatusCode.OK)] - public async Task GetDatesChange(Guid idDiscriminator, CancellationToken token) + public async Task GetDatesChange(Guid idDiscriminator, CancellationToken token = default) { var result = await repository.GetDatesChange(idDiscriminator, token); @@ -157,10 +162,13 @@ public class ChangeLogController : ControllerBase, IChangeLogApi [HttpGet("datesRange")] [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)] - public async Task GetDatesRangeAsync(Guid idDiscriminator, CancellationToken token) + public async Task GetDatesRangeAsync(Guid idDiscriminator, CancellationToken token = default) { var result = await repository.GetDatesRange(idDiscriminator, token); + if(result is null) + return NoContent(); + return Ok(result); } } diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs index 808d50e..eebb665 100644 --- a/Persistence.API/DependencyInjection.cs +++ b/Persistence.API/DependencyInjection.cs @@ -1,12 +1,10 @@ -using System.Security.Claims; -using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Persistence.Models.Configurations; using Swashbuckle.AspNetCore.SwaggerGen; +using System.Text.Json.Nodes; namespace Persistence.API; @@ -35,169 +33,163 @@ public static class DependencyInjection c.SwaggerDoc("v1", new OpenApiInfo { Title = "Persistence web api", Version = "v1" }); - var needUseKeyCloak = configuration.GetSection("NeedUseKeyCloak").Get(); - if (needUseKeyCloak) - c.AddKeycloackSecurity(configuration); - else c.AddDefaultSecurity(configuration); + 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; - //options.IncludeXmlComments(xmlPath, includeControllerXmlComment); - //options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment); - }); + //var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + //var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + //var includeControllerXmlComment = true; + //c.IncludeXmlComments(xmlPath, includeControllerXmlComment); + //c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment); + }); } - #region Authentication - public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration) + #region Authentication + public static void AddJWTAuthentication(this IServiceCollection services, IConfiguration configuration) { var needUseKeyCloak = configuration - .GetSection("NeedUseKeyCloak") - .Get(); - if (needUseKeyCloak) - services.AddKeyCloakAuthentication(configuration); - else services.AddDefaultAuthentication(configuration); - } + .GetSection("NeedUseKeyCloak") + .Get(); + if (needUseKeyCloak) + services.AddKeyCloakAuthentication(configuration); + else services.AddDefaultAuthentication(configuration); + } - private static void AddKeyCloakAuthentication(this IServiceCollection services, IConfiguration 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"], - }; + .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(); + 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; + context.Token = accessToken; - return Task.CompletedTask; - }, - OnTokenValidated = context => - { - if(context.Principal != null && context.Principal.HasClaim(x => x.Type != ClaimTypes.NameIdentifier)) - { - var claim = new Claim(ClaimTypes.NameIdentifier.ToString(), Guid.NewGuid().ToString()); - (context.Principal.Identity as ClaimsIdentity)!.AddClaim(claim); + 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"); } - var username = context.Principal?.Claims - .FirstOrDefault(e => e.Type == "username")?.Value; + return Task.CompletedTask; + } + }; + }); + } + #endregion - var password = context.Principal?.Claims - .FirstOrDefault(e => e.Type == "password")?.Value; + #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"]), + } + } + }); - var keyCloakUser = configuration - .GetSection(nameof(AuthUser)) - .Get()!; + options.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Keycloack" + }, + Scheme = "Bearer", + Name = "Bearer", + In = ParameterLocation.Header, + }, + new List() + } + }); + } - if (username != keyCloakUser.Username || password != keyCloakUser.Password) - { - context.Fail("username or password did not match"); - } + 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", + }); - 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 + 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.Database.Postgres/DependencyInjection.cs b/Persistence.Database.Postgres/DependencyInjection.cs index 0fec635..bbd936b 100644 --- a/Persistence.Database.Postgres/DependencyInjection.cs +++ b/Persistence.Database.Postgres/DependencyInjection.cs @@ -11,12 +11,8 @@ public static class DependencyInjection { string connectionStringName = "DefaultConnection"; - services.AddDbContext(options => - { - var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString(connectionStringName)); - dataSourceBuilder.EnableDynamicJson(); - options.UseNpgsql(dataSourceBuilder.Build()); - }); + services.AddDbContext(options => + options.UseNpgsql(configuration.GetConnectionString(connectionStringName))); services.AddScoped(provider => provider.GetRequiredService()); diff --git a/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs b/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs index 72f63da..39ea3f8 100644 --- a/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs +++ b/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs @@ -78,39 +78,46 @@ public class ChangeLogControllerTest : BaseIntegrationTest var idDiscriminator = Guid.NewGuid(); var dtos = Generate(1, DateTimeOffset.UtcNow); var dto = dtos.FirstOrDefault()!; - var entity = dto.Adapt(); - dbContext.ChangeLog.Add(entity); - dbContext.SaveChanges(); + var result = await client.Add(idDiscriminator, dto); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); - dto.Id = entity.Id; - dto.DepthStart = dto.DepthStart + 10; + var entity = dbContext.ChangeLog + .Where(x => x.IdDiscriminator == idDiscriminator) + .FirstOrDefault(); + dto = entity.Adapt(); dto.DepthEnd = dto.DepthEnd + 10; // act - var result = await client.Update(idDiscriminator, dto); + result = await client.Update(idDiscriminator, dto); // assert Assert.Equal(HttpStatusCode.OK, result.StatusCode); Assert.Equal(2, result.Content); - //var entities = dbContext.ChangeLog - // .Where(e => e.IdDiscriminator == idDiscriminator) - // .ToArray(); - //var obsoleteEntity = entities - // .Where(e => e.Obsolete.HasValue) - // .FirstOrDefault(); + var dateBegin = DateTimeOffset.UtcNow.AddDays(-1); + var dateEnd = DateTimeOffset.UtcNow.AddDays(1); - //var activeEntity = entities - // .Where(e => !e.Obsolete.HasValue) - // .FirstOrDefault(); + var changeLogResult = await client.GetChangeLogForDate(idDiscriminator, dateBegin, dateEnd); + Assert.Equal(HttpStatusCode.OK, changeLogResult.StatusCode); + Assert.NotNull(changeLogResult.Content); - //if (obsoleteEntity == null || activeEntity == null) - //{ - // Assert.Fail(); - // return; - //} + var changeLogDtos = changeLogResult.Content; - //Assert.Equal(activeEntity.IdNext, obsoleteEntity.Id); + var obsoleteDto = changeLogDtos + .Where(e => e.Obsolete.HasValue) + .FirstOrDefault(); + + var activeDto = changeLogDtos + .Where(e => !e.Obsolete.HasValue) + .FirstOrDefault(); + + if (obsoleteDto == null || activeDto == null) + { + Assert.Fail(); + return; + } + + Assert.Equal(activeDto.Id, obsoleteDto.IdNext); } @@ -185,11 +192,7 @@ public class ChangeLogControllerTest : BaseIntegrationTest // arrange var changeLogItems = CreateChangeLogItems(3, (-15, 15)); var idDiscriminator = changeLogItems.Item1; - var entities = changeLogItems.Item2; - - var orderedEntities = entities.OrderBy(e => e.Creation); - var minDate = orderedEntities.First().Creation; - var maxDate = orderedEntities.Last().Creation; + var entities = changeLogItems.Item2.OrderBy(e => e.Creation); // act var result = await client.GetDatesRange(idDiscriminator); @@ -198,12 +201,15 @@ public class ChangeLogControllerTest : BaseIntegrationTest Assert.Equal(HttpStatusCode.OK, result.StatusCode); Assert.NotNull(result.Content); + var minDate = entities.First().Creation; + var maxDate = entities.Last().Creation; + var expectedMinDate = minDate.ToUniversalTime().ToString(); - var actualMinDate = result.Content!.From.ToUniversalTime().ToString(); + var actualMinDate = result.Content.From.ToUniversalTime().ToString(); Assert.Equal(expectedMinDate, actualMinDate); var expectedMaxDate = maxDate.ToUniversalTime().ToString(); - var actualMaxDate = result.Content!.To.ToUniversalTime().ToString(); + var actualMaxDate = result.Content.To.ToUniversalTime().ToString(); Assert.Equal(expectedMaxDate, actualMaxDate); } diff --git a/Persistence.Repository/Repositories/ChangeLogRepository.cs b/Persistence.Repository/Repositories/ChangeLogRepository.cs index 7b92039..514fed5 100644 --- a/Persistence.Repository/Repositories/ChangeLogRepository.cs +++ b/Persistence.Repository/Repositories/ChangeLogRepository.cs @@ -232,11 +232,6 @@ public class ChangeLogRepository : IChangeLogRepository return datesOnly; } - public Task>> GetGtDate(DateTimeOffset dateBegin, CancellationToken token) - { - throw new NotImplementedException(); - } - private ChangeLog CreateEntityFromDto(Guid idUser, Guid idDiscriminator, DataWithWellDepthAndSectionDto dto) { var entity = new ChangeLog() @@ -273,17 +268,25 @@ public class ChangeLogRepository : IChangeLogRepository public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) { var query = db.Set() - .Where(e => e.IdDiscriminator == idDiscriminator); - var test = db.Set().ToArray(); - var test2 = query.ToArray(); + .Where(e => e.IdDiscriminator == idDiscriminator) + .GroupBy(e => 1) + .Select(group => new + { + Min = group.Min(e => e.Creation), + Max = group.Max(e => e.Creation), + }); - var minDate = await query.MinAsync(o => o.Creation, token); - var maxDate = await query.MaxAsync(o => o.Creation, token); + var values = await query.FirstOrDefaultAsync(token); + + if(values is null) + { + return null; + } return new DatesRangeDto { - From = minDate, - To = maxDate + From = values.Min, + To = values.Max, }; } } diff --git a/Persistence/Models/ChangeLogDto.cs b/Persistence/Models/ChangeLogDto.cs index aa8963f..8e28874 100644 --- a/Persistence/Models/ChangeLogDto.cs +++ b/Persistence/Models/ChangeLogDto.cs @@ -10,6 +10,11 @@ public class ChangeLogDto } + /// + /// Ключ записи + /// + public Guid Id { get; set; } + /// /// Создатель записи /// @@ -38,5 +43,5 @@ public class ChangeLogDto /// /// Объект записи /// - public DataWithWellDepthAndSectionDto Value { get; set; } + public DataWithWellDepthAndSectionDto Value { get; set; } = default!; }