fix(configuration-database): resolve ConfigurationDatabase-005,006,008,009,010,011 — bounded gRPC columns, split queries, CSV-parse logging, null guards, coverage
This commit is contained in:
@@ -23,6 +23,8 @@ public class SiteConfiguration : IEntityTypeConfiguration<Site>
|
||||
|
||||
builder.Property(s => s.NodeAAddress).HasMaxLength(500);
|
||||
builder.Property(s => s.NodeBAddress).HasMaxLength(500);
|
||||
builder.Property(s => s.GrpcNodeAAddress).HasMaxLength(500);
|
||||
builder.Property(s => s.GrpcNodeBAddress).HasMaxLength(500);
|
||||
|
||||
builder.HasIndex(s => s.Name).IsUnique();
|
||||
builder.HasIndex(s => s.SiteIdentifier).IsUnique();
|
||||
|
||||
1350
src/ScadaLink.ConfigurationDatabase/Migrations/20260517020720_BoundGrpcNodeAddressLength.Designer.cs
generated
Normal file
1350
src/ScadaLink.ConfigurationDatabase/Migrations/20260517020720_BoundGrpcNodeAddressLength.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BoundGrpcNodeAddressLength : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "GrpcNodeBAddress",
|
||||
table: "Sites",
|
||||
type: "nvarchar(500)",
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "GrpcNodeAAddress",
|
||||
table: "Sites",
|
||||
type: "nvarchar(500)",
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)",
|
||||
oldNullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "GrpcNodeBAddress",
|
||||
table: "Sites",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(500)",
|
||||
oldMaxLength: 500,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "GrpcNodeAAddress",
|
||||
table: "Sites",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(500)",
|
||||
oldMaxLength: 500,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -830,10 +830,12 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<string>("GrpcNodeAAddress")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("GrpcNodeBAddress")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
|
||||
@@ -50,6 +50,7 @@ public class CentralUiRepository : ICentralUiRepository
|
||||
.Include(t => t.Alarms)
|
||||
.Include(t => t.Scripts)
|
||||
.Include(t => t.Compositions)
|
||||
.AsSplitQuery()
|
||||
.OrderBy(t => t.Name)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -171,6 +171,7 @@ public class DeploymentManagerRepository : IDeploymentManagerRepository
|
||||
.Include(i => i.AttributeOverrides)
|
||||
.Include(i => i.AlarmOverrides)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(i => i.Id == instanceId, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -180,6 +181,7 @@ public class DeploymentManagerRepository : IDeploymentManagerRepository
|
||||
.Include(i => i.AttributeOverrides)
|
||||
.Include(i => i.AlarmOverrides)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(i => i.UniqueName == uniqueName, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ExternalSystemRepository : IExternalSystemRepository
|
||||
|
||||
public ExternalSystemRepository(ScadaLinkDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<ExternalSystemDefinition?> GetExternalSystemByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ScadaLink.Commons.Entities.InboundApi;
|
||||
using ScadaLink.Commons.Interfaces.Repositories;
|
||||
|
||||
@@ -7,10 +9,12 @@ namespace ScadaLink.ConfigurationDatabase.Repositories;
|
||||
public class InboundApiRepository : IInboundApiRepository
|
||||
{
|
||||
private readonly ScadaLinkDbContext _context;
|
||||
private readonly ILogger<InboundApiRepository> _logger;
|
||||
|
||||
public InboundApiRepository(ScadaLinkDbContext context)
|
||||
public InboundApiRepository(ScadaLinkDbContext context, ILogger<InboundApiRepository>? logger = null)
|
||||
{
|
||||
_context = context;
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_logger = logger ?? NullLogger<InboundApiRepository>.Instance;
|
||||
}
|
||||
|
||||
public async Task<ApiKey?> GetApiKeyByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
@@ -49,10 +53,26 @@ public class InboundApiRepository : IInboundApiRepository
|
||||
if (method?.ApprovedApiKeyIds == null)
|
||||
return new List<ApiKey>();
|
||||
|
||||
var keyIds = method.ApprovedApiKeyIds.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(s => int.TryParse(s.Trim(), out var id) ? id : -1)
|
||||
.Where(id => id > 0)
|
||||
.ToList();
|
||||
// ApprovedApiKeyIds is a comma-separated string of integer ApiKey ids. A token that
|
||||
// fails to parse indicates a corrupt value: it is dropped (it cannot identify a key),
|
||||
// but the corruption is logged as a warning so it is observable rather than silent.
|
||||
// A corrupt list would otherwise quietly approve fewer keys than intended.
|
||||
var keyIds = new List<int>();
|
||||
foreach (var token in method.ApprovedApiKeyIds.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var trimmed = token.Trim();
|
||||
if (int.TryParse(trimmed, out var id) && id > 0)
|
||||
{
|
||||
keyIds.Add(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"ApiMethod {MethodId} has a malformed approved-API-key id token '{Token}' " +
|
||||
"in ApprovedApiKeyIds; it was dropped. The method may approve fewer keys than expected.",
|
||||
methodId, trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
return await _context.Set<ApiKey>().Where(k => keyIds.Contains(k.Id)).ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class NotificationRepository : INotificationRepository
|
||||
|
||||
public NotificationRepository(ScadaLinkDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<NotificationList?> GetNotificationListByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -24,6 +24,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(t => t.Alarms)
|
||||
.Include(t => t.Scripts)
|
||||
.Include(t => t.Compositions)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -45,6 +46,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(t => t.Alarms)
|
||||
.Include(t => t.Scripts)
|
||||
.Include(t => t.Compositions)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -55,6 +57,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(t => t.Attributes)
|
||||
.Include(t => t.Scripts)
|
||||
.Include(t => t.Compositions)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -222,6 +225,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(i => i.AttributeOverrides)
|
||||
.Include(i => i.AlarmOverrides)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(i => i.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -231,6 +235,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(i => i.AttributeOverrides)
|
||||
.Include(i => i.AlarmOverrides)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -248,6 +253,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(i => i.AttributeOverrides)
|
||||
.Include(i => i.AlarmOverrides)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -257,6 +263,7 @@ public class TemplateEngineRepository : ITemplateEngineRepository
|
||||
.Include(i => i.AttributeOverrides)
|
||||
.Include(i => i.AlarmOverrides)
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(i => i.UniqueName == uniqueName, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" />
|
||||
|
||||
@@ -12,7 +12,7 @@ public class InstanceLocator : IInstanceLocator
|
||||
|
||||
public InstanceLocator(ScadaLinkDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public async Task<string?> GetSiteIdForInstanceAsync(
|
||||
|
||||
Reference in New Issue
Block a user