feat: Aggiunta gestione nome database sorgente nei profili DataCoupler

 Nuove funzionalità:
- Aggiunto campo SourceDatabaseName nella tabella DataCouplerProfiles
- Implementato recupero automatico del nome database dalle credenziali
- Migliorata applicazione profili con supporto database specifico
- Aggiornata logica di connessione database con selezione database

🔧 Modifiche tecniche:
- Aggiunta migration per colonna SourceDatabaseName
- Estesi modelli DataCouplerProfile e DataCouplerProfileDto
- Aggiornato DataCouplerProfileService per gestire nuovo campo
- Modificato ProfileSaver per recupero automatico database name
- Implementato metodo ConnectToDatabaseWithSpecificDatabase

🐛 Correzioni:
- Migliorata gestione connessioni database multi-database
- Corretta formattazione codice e spaziature
- Rimosse linee vuote eccessive nel codice sorgente

🧪
This commit is contained in:
2025-07-05 18:10:09 +02:00
parent 65ed2bb93a
commit 7d2961702c
21 changed files with 1002 additions and 367 deletions
+20
View File
@@ -44,6 +44,22 @@
</div> </div>
</div> </div>
<!-- Fonte -->
<div class="mb-3">
<label class="form-label">Fonte</label>
<div class="bg-light p-3 rounded">
@* Contenuto esistente per la fonte *@
</div>
</div>
<!-- Destinazione -->
<div class="mb-3">
<label class="form-label">Destinazione</label>
<div class="bg-light p-3 rounded">
@* Contenuto esistente per la destinazione *@
</div>
</div>
@if (!string.IsNullOrEmpty(SaveMessage)) @if (!string.IsNullOrEmpty(SaveMessage))
{ {
<div class="alert alert-@(SaveMessageType) mb-3"> <div class="alert alert-@(SaveMessageType) mb-3">
@@ -63,6 +79,10 @@
{ {
<span class="text-muted">Credenziali: @SourceCredentialName</span><br /> <span class="text-muted">Credenziali: @SourceCredentialName</span><br />
} }
@if (!string.IsNullOrEmpty(SourceDatabaseName))
{
<span class="text-muted">Database: @SourceDatabaseName <em>(dalla connessione attiva)</em></span><br />
}
@if (!string.IsNullOrEmpty(SourceSchema)) @if (!string.IsNullOrEmpty(SourceSchema))
{ {
<span class="text-muted">Schema: @SourceSchema</span><br /> <span class="text-muted">Schema: @SourceSchema</span><br />
+36
View File
@@ -1,15 +1,19 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using CredentialManager.Models; using CredentialManager.Models;
using CredentialManager.Services;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Components; namespace Components;
public partial class ProfileSaver public partial class ProfileSaver
{ {
[Inject] private ICredentialService CredentialService { get; set; } = default!;
[Parameter] public bool CanSave { get; set; } [Parameter] public bool CanSave { get; set; }
[Parameter] public string SourceType { get; set; } = ""; [Parameter] public string SourceType { get; set; } = "";
[Parameter] public int? SourceCredentialId { get; set; } [Parameter] public int? SourceCredentialId { get; set; }
[Parameter] public string? SourceCredentialName { get; set; } [Parameter] public string? SourceCredentialName { get; set; }
[Parameter] public string? SourceDatabaseName { get; set; }
[Parameter] public string? SourceSchema { get; set; } [Parameter] public string? SourceSchema { get; set; }
[Parameter] public string? SourceTable { get; set; } [Parameter] public string? SourceTable { get; set; }
[Parameter] public string? SourceFilePath { get; set; } [Parameter] public string? SourceFilePath { get; set; }
@@ -51,6 +55,9 @@ public partial class ProfileSaver
try try
{ {
// Recupera automaticamente il nome del database dalla connessione attiva
var sourceDatabaseName = await GetSourceDatabaseNameAsync();
var profileDto = new DataCouplerProfileDto var profileDto = new DataCouplerProfileDto
{ {
Name = ProfileData.Name, Name = ProfileData.Name,
@@ -58,6 +65,7 @@ public partial class ProfileSaver
SourceType = SourceType, SourceType = SourceType,
SourceCredentialId = SourceCredentialId, SourceCredentialId = SourceCredentialId,
SourceCredentialName = SourceCredentialName, SourceCredentialName = SourceCredentialName,
SourceDatabaseName = sourceDatabaseName,
SourceSchema = SourceSchema, SourceSchema = SourceSchema,
SourceTable = SourceTable, SourceTable = SourceTable,
SourceFilePath = SourceFilePath, SourceFilePath = SourceFilePath,
@@ -119,6 +127,34 @@ public partial class ProfileSaver
SaveMessageType = type; SaveMessageType = type;
} }
private async Task<string?> GetSourceDatabaseNameAsync()
{
// Prima priorità: se SourceDatabaseName è già impostato come parametro, usa quello
if (!string.IsNullOrEmpty(SourceDatabaseName))
{
return SourceDatabaseName;
}
// Seconda priorità: se abbiamo un SourceCredentialId, recupera il database dalle credenziali
if (SourceCredentialId.HasValue)
{
try
{
var credential = await CredentialService.GetDatabaseCredentialAsync(SourceCredentialId.Value);
if (credential != null && !string.IsNullOrEmpty(credential.DatabaseName))
{
return credential.DatabaseName;
}
}
catch (Exception)
{
// Se non riesce a recuperare le credenziali, continua con null
}
}
return null;
}
public class ProfileFormModel public class ProfileFormModel
{ {
[Required(ErrorMessage = "Il nome del profilo è obbligatorio")] [Required(ErrorMessage = "Il nome del profilo è obbligatorio")]
@@ -0,0 +1,337 @@
// <auto-generated />
using System;
using CredentialManager.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CredentialManager.Migrations
{
[DbContext(typeof(CredentialDbContext))]
[Migration("20250704135720_AddSourceDatabaseNameColumn")]
partial class AddSourceDatabaseNameColumn
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalParameters")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<int>("CommandTimeout")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(30);
b.Property<string>("ConnectionString")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("DatabaseName")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("DatabaseType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("EncryptedApiKey")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("EncryptedAuthToken")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("EncryptedPassword")
.HasColumnType("TEXT");
b.Property<string>("Headers")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("Host")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<bool>("IgnoreSslErrors")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(false);
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int?>("Port")
.HasColumnType("INTEGER");
b.Property<string>("RestServiceType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<int>("TimeoutSeconds")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(100);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("Username")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("DatabaseType");
b.HasIndex("IsActive");
b.HasIndex("Name")
.IsUnique();
b.HasIndex("Type");
b.ToTable("Credentials", (string)null);
});
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<string>("CreatedBy")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<int?>("DestinationCredentialId")
.HasColumnType("INTEGER");
b.Property<string>("DestinationEndpoint")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationSchema")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("DestinationTable")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("DestinationType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("FieldMappingJson")
.HasMaxLength(4000)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<DateTime?>("LastUsedAt")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int?>("SourceCredentialId")
.HasColumnType("INTEGER");
b.Property<string>("SourceDatabaseName")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceFilePath")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceKeyField")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceSchema")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceTable")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<bool>("UseRecordAssociations")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("DestinationCredentialId");
b.HasIndex("DestinationType");
b.HasIndex("IsActive");
b.HasIndex("LastUsedAt");
b.HasIndex("Name")
.IsUnique();
b.HasIndex("SourceCredentialId");
b.HasIndex("SourceType");
b.ToTable("DataCouplerProfiles", (string)null);
});
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalInfo")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("DestinationEntity")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("DestinationId")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("DestinationKeyField")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<string>("KeyValue")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime?>("LastVerifiedAt")
.HasColumnType("TEXT");
b.Property<string>("RestCredentialName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("SourceKeyField")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourcesInfo")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("CreatedAt");
b.HasIndex("DestinationEntity");
b.HasIndex("IsActive");
b.HasIndex("KeyValue")
.HasDatabaseName("IX_KeyAssociations_KeyValue");
b.HasIndex("LastVerifiedAt");
b.HasIndex("RestCredentialName");
b.HasIndex("KeyValue", "DestinationEntity", "RestCredentialName")
.IsUnique()
.HasDatabaseName("IX_KeyAssociations_Unique");
b.ToTable("KeyAssociations", (string)null);
});
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
{
b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
.WithMany()
.HasForeignKey("DestinationCredentialId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
.WithMany()
.HasForeignKey("SourceCredentialId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("DestinationCredential");
b.Navigation("SourceCredential");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CredentialManager.Migrations
{
/// <inheritdoc />
public partial class AddSourceDatabaseNameColumn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SourceDatabaseName",
table: "DataCouplerProfiles",
type: "TEXT",
maxLength: 200,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SourceDatabaseName",
table: "DataCouplerProfiles");
}
}
}
@@ -182,6 +182,10 @@ namespace CredentialManager.Migrations
b.Property<int?>("SourceCredentialId") b.Property<int?>("SourceCredentialId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("SourceDatabaseName")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceFilePath") b.Property<string>("SourceFilePath")
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@@ -25,6 +25,9 @@ public class DataCouplerProfile
public int? SourceCredentialId { get; set; } public int? SourceCredentialId { get; set; }
[MaxLength(200)]
public string? SourceDatabaseName { get; set; }
[MaxLength(200)] [MaxLength(200)]
public string? SourceSchema { get; set; } public string? SourceSchema { get; set; }
@@ -13,6 +13,7 @@ public class DataCouplerProfileDto
public string SourceType { get; set; } = string.Empty; public string SourceType { get; set; } = string.Empty;
public int? SourceCredentialId { get; set; } public int? SourceCredentialId { get; set; }
public string? SourceCredentialName { get; set; } public string? SourceCredentialName { get; set; }
public string? SourceDatabaseName { get; set; }
public string? SourceSchema { get; set; } public string? SourceSchema { get; set; }
public string? SourceTable { get; set; } public string? SourceTable { get; set; }
public string? SourceFilePath { get; set; } public string? SourceFilePath { get; set; }
@@ -214,6 +214,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
SourceType = profile.SourceType, SourceType = profile.SourceType,
SourceCredentialId = profile.SourceCredentialId, SourceCredentialId = profile.SourceCredentialId,
SourceCredentialName = profile.SourceCredential?.Name, SourceCredentialName = profile.SourceCredential?.Name,
SourceDatabaseName = profile.SourceDatabaseName,
SourceSchema = profile.SourceSchema, SourceSchema = profile.SourceSchema,
SourceTable = profile.SourceTable, SourceTable = profile.SourceTable,
SourceFilePath = profile.SourceFilePath, SourceFilePath = profile.SourceFilePath,
@@ -241,6 +242,7 @@ public class DataCouplerProfileService : IDataCouplerProfileService
Description = dto.Description, Description = dto.Description,
SourceType = dto.SourceType, SourceType = dto.SourceType,
SourceCredentialId = dto.SourceCredentialId, SourceCredentialId = dto.SourceCredentialId,
SourceDatabaseName = dto.SourceDatabaseName,
SourceSchema = dto.SourceSchema, SourceSchema = dto.SourceSchema,
SourceTable = dto.SourceTable, SourceTable = dto.SourceTable,
SourceFilePath = dto.SourceFilePath, SourceFilePath = dto.SourceFilePath,
Binary file not shown.
+1
View File
@@ -1005,6 +1005,7 @@
SourceType="@selectedSourceType" SourceType="@selectedSourceType"
SourceCredentialId="@(GetCurrentSourceCredentialIdAsync().Result)" SourceCredentialId="@(GetCurrentSourceCredentialIdAsync().Result)"
SourceCredentialName="@selectedDatabaseCredential" SourceCredentialName="@selectedDatabaseCredential"
SourceDatabaseName="@selectedDatabase"
SourceSchema="@GetCurrentDatabaseSchema()" SourceSchema="@GetCurrentDatabaseSchema()"
SourceTable="@(useCustomQuery ? "custom_query" : selectedTable)" SourceTable="@(useCustomQuery ? "custom_query" : selectedTable)"
SourceFilePath="@selectedFileName" SourceFilePath="@selectedFileName"
+90 -33
View File
@@ -102,7 +102,7 @@ public partial class DataCoupler : ComponentBase
private RestEntitySummary? selectedRestEntity = null; private RestEntitySummary? selectedRestEntity = null;
private RestEntityInfo? restEntityDetails = null; private RestEntityInfo? restEntityDetails = null;
private string restSearchTerm = ""; private string restSearchTerm = "";
// Mapping campi // Mapping campi
private Dictionary<string, string> fieldMappings = new(); // DbColumn -> RestProperty private Dictionary<string, string> fieldMappings = new(); // DbColumn -> RestProperty
private HashSet<string> keyFields = new(); // REST properties marked as keys private HashSet<string> keyFields = new(); // REST properties marked as keys
private string selectedDbColumn = ""; private string selectedDbColumn = "";
@@ -134,7 +134,8 @@ public partial class DataCoupler : ComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadCredentials(); await LoadCredentials();
} private async Task LoadCredentials() }
private async Task LoadCredentials()
{ {
try try
{ {
@@ -235,14 +236,21 @@ public partial class DataCoupler : ComponentBase
// Connetti al database // Connetti al database
Logger.LogInformation("Iniziando connessione database..."); Logger.LogInformation("Iniziando connessione database...");
if (!string.IsNullOrEmpty(profile.SourceSchema))
// Gestione connessione con database specifico
if (!string.IsNullOrEmpty(profile.SourceDatabaseName))
{
Logger.LogInformation("Connessione con database specifico: {Database}", profile.SourceDatabaseName);
await ConnectToDatabaseWithSpecificDatabase(profile.SourceDatabaseName);
}
else if (!string.IsNullOrEmpty(profile.SourceSchema))
{ {
Logger.LogInformation("Connessione con schema specifico: {Schema}", profile.SourceSchema); Logger.LogInformation("Connessione con schema specifico: {Schema}", profile.SourceSchema);
await ConnectToDatabaseWithSchema(profile.SourceSchema); await ConnectToDatabaseWithSchema(profile.SourceSchema);
} }
else else
{ {
Logger.LogInformation("Connessione senza schema specifico"); Logger.LogInformation("Connessione senza database/schema specifico");
await ConnectToDatabase(); await ConnectToDatabase();
} }
@@ -697,7 +705,9 @@ public partial class DataCoupler : ComponentBase
selectedSourceType = e.Value?.ToString() ?? ""; selectedSourceType = e.Value?.ToString() ?? "";
// Reset state when changing source type // Reset state when changing source type
ResetSourceState(); } private void ResetSourceState() ResetSourceState();
}
private void ResetSourceState()
{ {
// Reset database state // Reset database state
ResetDatabaseState(); ResetDatabaseState();
@@ -718,7 +728,8 @@ public partial class DataCoupler : ComponentBase
} }
private async Task OnFileSelected(InputFileChangeEventArgs e) private async Task OnFileSelected(InputFileChangeEventArgs e)
{ try {
try
{ {
isProcessingFile = true; isProcessingFile = true;
fileErrorMessage = ""; fileErrorMessage = "";
@@ -757,7 +768,8 @@ public partial class DataCoupler : ComponentBase
isProcessingFile = false; isProcessingFile = false;
StateHasChanged(); StateHasChanged();
} }
} private async Task ProcessCsvFile(IBrowserFile file) }
private async Task ProcessCsvFile(IBrowserFile file)
{ {
using var stream = file.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); // Aumentato a 50MB using var stream = file.OpenReadStream(maxAllowedSize: 50 * 1024 * 1024); // Aumentato a 50MB
using var reader = new StreamReader(stream); using var reader = new StreamReader(stream);
@@ -794,7 +806,7 @@ public partial class DataCoupler : ComponentBase
var values = ParseCsvLine(line, separator); var values = ParseCsvLine(line, separator);
var row = new Dictionary<string, object>(); var row = new Dictionary<string, object>();
for (int i = 0; i < headers.Count; i++) for (int i = 0; i < headers.Count; i++)
{ {
var value = i < values.Count ? values[i] : ""; var value = i < values.Count ? values[i] : "";
row[headers[i]] = string.IsNullOrEmpty(value) ? "" : value; row[headers[i]] = string.IsNullOrEmpty(value) ? "" : value;
@@ -809,14 +821,15 @@ public partial class DataCoupler : ComponentBase
Logger.LogInformation("CSV row {RowNumber}: {Values}", rowNumber - 1, string.Join(" | ", values)); Logger.LogInformation("CSV row {RowNumber}: {Values}", rowNumber - 1, string.Join(" | ", values));
} }
} }
fileData[sheetName] = dataRows; fileData[sheetName] = dataRows;
// Auto-seleziona il foglio per i CSV dato che ce n'è solo uno // Auto-seleziona il foglio per i CSV dato che ce n'è solo uno
selectedSheet = sheetName; selectedSheet = sheetName;
Logger.LogInformation("CSV file processed: {FileName}, Headers: {HeaderCount} ({Headers}), Rows: {RowCount}, Auto-selected sheet: {SheetName}", Logger.LogInformation("CSV file processed: {FileName}, Headers: {HeaderCount} ({Headers}), Rows: {RowCount}, Auto-selected sheet: {SheetName}",
file.Name, headers.Count, string.Join(", ", headers), dataRows.Count, selectedSheet); file.Name, headers.Count, string.Join(", ", headers), dataRows.Count, selectedSheet);
} private List<string> ParseCsvLine(string line, char separator = ',') }
private List<string> ParseCsvLine(string line, char separator = ',')
{ {
var result = new List<string>(); var result = new List<string>();
var current = new StringBuilder(); var current = new StringBuilder();
@@ -856,7 +869,8 @@ public partial class DataCoupler : ComponentBase
result.Add(current.ToString().Trim()); result.Add(current.ToString().Trim());
return result; return result;
}private async Task ProcessExcelFile(IBrowserFile file) }
private async Task ProcessExcelFile(IBrowserFile file)
{ {
try try
{ {
@@ -948,7 +962,8 @@ public partial class DataCoupler : ComponentBase
{ {
selectedSheet = fileSheets.First().Key; selectedSheet = fileSheets.First().Key;
Logger.LogInformation("Auto-selected first sheet: {SheetName}", selectedSheet); Logger.LogInformation("Auto-selected first sheet: {SheetName}", selectedSheet);
} Logger.LogInformation("Excel file processing completed: {FileName}, Total sheets: {SheetCount}, Selected: {SelectedSheet}", }
Logger.LogInformation("Excel file processing completed: {FileName}, Total sheets: {SheetCount}, Selected: {SelectedSheet}",
file.Name, fileSheets.Count, selectedSheet); file.Name, fileSheets.Count, selectedSheet);
} }
} }
@@ -959,7 +974,8 @@ public partial class DataCoupler : ComponentBase
} }
await Task.CompletedTask; await Task.CompletedTask;
} private void SelectSheet(string sheetName) }
private void SelectSheet(string sheetName)
{ {
selectedSheet = sheetName; selectedSheet = sheetName;
@@ -1018,7 +1034,8 @@ public partial class DataCoupler : ComponentBase
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
return 0; return 0;
return (currentPage - 1) * pageSize + 1; return (currentPage - 1) * pageSize + 1;
} private int GetEndRecord() }
private int GetEndRecord()
{ {
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
return 0; return 0;
@@ -1035,11 +1052,13 @@ public partial class DataCoupler : ComponentBase
currentPage = 1; // Reset to first page when changing page size currentPage = 1; // Reset to first page when changing page size
StateHasChanged(); StateHasChanged();
} }
}private void OnDatabaseCredentialChanged(ChangeEventArgs e) }
private void OnDatabaseCredentialChanged(ChangeEventArgs e)
{ {
selectedDatabaseCredential = e.Value?.ToString() ?? ""; selectedDatabaseCredential = e.Value?.ToString() ?? "";
ResetDatabaseState(); ResetDatabaseState();
} private void OnRestCredentialChanged(ChangeEventArgs e) }
private void OnRestCredentialChanged(ChangeEventArgs e)
{ {
var newCredential = e.Value?.ToString() ?? ""; var newCredential = e.Value?.ToString() ?? "";
@@ -1052,7 +1071,8 @@ public partial class DataCoupler : ComponentBase
selectedRestCredential = newCredential; selectedRestCredential = newCredential;
ResetRestState(); ResetRestState();
} private void ResetDatabaseState() }
private void ResetDatabaseState()
{ {
isDatabaseConnected = false; isDatabaseConnected = false;
databaseTables.Clear(); databaseTables.Clear();
@@ -1082,7 +1102,8 @@ public partial class DataCoupler : ComponentBase
// Clear mappings when resetting database state // Clear mappings when resetting database state
ClearAllMappings(); ClearAllMappings();
} private void ResetRestState() }
private void ResetRestState()
{ {
isRestConnected = false; isRestConnected = false;
restEntities.Clear(); restEntities.Clear();
@@ -1095,7 +1116,8 @@ public partial class DataCoupler : ComponentBase
// Clear mappings when resetting REST state // Clear mappings when resetting REST state
ClearAllMappings(); ClearAllMappings();
} private async Task ConnectToDatabase() }
private async Task ConnectToDatabase()
{ {
if (string.IsNullOrEmpty(selectedDatabaseCredential)) if (string.IsNullOrEmpty(selectedDatabaseCredential))
return; return;
@@ -1175,7 +1197,8 @@ public partial class DataCoupler : ComponentBase
isConnectingDatabase = false; isConnectingDatabase = false;
StateHasChanged(); StateHasChanged();
} }
}private async Task ConnectToRestApi() }
private async Task ConnectToRestApi()
{ {
if (string.IsNullOrEmpty(selectedRestCredential)) if (string.IsNullOrEmpty(selectedRestCredential))
return; return;
@@ -1201,7 +1224,7 @@ public partial class DataCoupler : ComponentBase
return; return;
} // Crea i client REST usando il factory con le credenziali complete } // Crea i client REST usando il factory con le credenziali complete
currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential); currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential);
currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential); Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential); currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential); Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential);
// Autenticazione prima del discovery // Autenticazione prima del discovery
var authResult = await currentRestClient.AuthenticateAsync(); var authResult = await currentRestClient.AuthenticateAsync();
@@ -1237,7 +1260,8 @@ public partial class DataCoupler : ComponentBase
{ {
isConnectingRest = false; isConnectingRest = false;
} }
} private async Task SelectTable(string tableName) }
private async Task SelectTable(string tableName)
{ {
selectedTable = tableName; selectedTable = tableName;
@@ -1326,7 +1350,8 @@ public partial class DataCoupler : ComponentBase
if (currentRestDiscovery != null) if (currentRestDiscovery != null)
{ {
// Discovery dei dettagli dell'entità // Discovery dei dettagli dell'entità
restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name); } restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name);
}
else else
{ {
restErrorMessage = "Servizio di discovery REST non disponibile"; restErrorMessage = "Servizio di discovery REST non disponibile";
@@ -1337,7 +1362,8 @@ public partial class DataCoupler : ComponentBase
{ {
Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name); Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name);
restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}"; restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}";
} } }
}
// Metodi per la ricerca e il filtraggio // Metodi per la ricerca e il filtraggio
private IEnumerable<string> GetFilteredDatabaseTables() private IEnumerable<string> GetFilteredDatabaseTables()
@@ -1422,7 +1448,8 @@ public partial class DataCoupler : ComponentBase
fieldMappings.Remove(selectedDbColumn); fieldMappings.Remove(selectedDbColumn);
Logger.LogInformation("Rimosso mapping per campo: {DbColumn}", selectedDbColumn); Logger.LogInformation("Rimosso mapping per campo: {DbColumn}", selectedDbColumn);
} private void RemoveSpecificMapping(string dbColumn) }
private void RemoveSpecificMapping(string dbColumn)
{ {
if (fieldMappings.ContainsKey(dbColumn)) if (fieldMappings.ContainsKey(dbColumn))
{ {
@@ -1490,7 +1517,8 @@ public partial class DataCoupler : ComponentBase
Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici da {SourceType}", Logger.LogInformation("Auto-mapping completato. Creati {Count} mapping automatici da {SourceType}",
mappingsCreated, useCustomQuery ? "query custom" : selectedSourceType); mappingsCreated, useCustomQuery ? "query custom" : selectedSourceType);
} private async Task ShowMappingSummary() }
private async Task ShowMappingSummary()
{ {
var summary = "Riepilogo Configurazione:\n\n"; var summary = "Riepilogo Configurazione:\n\n";
summary += "=== MAPPING CAMPI ===\n"; summary += "=== MAPPING CAMPI ===\n";
@@ -1507,7 +1535,8 @@ public partial class DataCoupler : ComponentBase
} }
await JSRuntime.InvokeVoidAsync("alert", summary); await JSRuntime.InvokeVoidAsync("alert", summary);
} private async Task StartDataTransfer() }
private async Task StartDataTransfer()
{ {
if (!fieldMappings.Any() || currentRestClient == null || selectedRestEntity == null) if (!fieldMappings.Any() || currentRestClient == null || selectedRestEntity == null)
{ {
@@ -1697,7 +1726,7 @@ public partial class DataCoupler : ComponentBase
goto HandleInvalidAssociation; goto HandleInvalidAssociation;
} }
HandleInvalidAssociation: HandleInvalidAssociation:
// L'ID di destinazione non esiste più o l'update è fallito - elimina l'associazione non valida // L'ID di destinazione non esiste più o l'update è fallito - elimina l'associazione non valida
try try
{ {
@@ -1837,7 +1866,8 @@ public partial class DataCoupler : ComponentBase
{ {
isTransferringData = false; isTransferringData = false;
} }
} private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromSource() }
private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromSource()
{ {
if (selectedSourceType == "database") if (selectedSourceType == "database")
{ {
@@ -1894,7 +1924,8 @@ public partial class DataCoupler : ComponentBase
useCustomQuery, selectedTable, useCustomQuery ? customQuery : "N/A"); useCustomQuery, selectedTable, useCustomQuery ? customQuery : "N/A");
throw; throw;
} }
} private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromFile() }
private async Task<IEnumerable<Dictionary<string, object>>> GetAllRecordsFromFile()
{ {
if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet)) if (string.IsNullOrEmpty(selectedSheet) || !fileData.ContainsKey(selectedSheet))
{ {
@@ -2220,8 +2251,8 @@ public partial class DataCoupler : ComponentBase
if (databaseTables.Count == 0) if (databaseTables.Count == 0)
{ {
// Se non ci sono tabelle, potrebbe essere necessario selezionare un database specifico // Se non ci sono tabelle, potrebbe essere necessario selezionare un database specifico
// Schema discovery completato senza successo // Schema discovery completato senza successo
databaseErrorMessage = "Impossibile rilevare le tabelle del database. Verificare le credenziali di connessione."; databaseErrorMessage = "Impossibile rilevare le tabelle del database. Verificare le credenziali di connessione.";
} }
else else
{ {
@@ -2549,7 +2580,7 @@ public partial class DataCoupler : ComponentBase
{ {
return databaseType switch return databaseType switch
{ {
DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery", DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery",
DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}", DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}",
DatabaseType.MySql => $"{baseQuery} LIMIT {limit}", DatabaseType.MySql => $"{baseQuery} LIMIT {limit}",
DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}", DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}",
@@ -3066,8 +3097,34 @@ public partial class DataCoupler : ComponentBase
private async Task ConnectToDatabaseWithSpecificDatabase(string databaseName) private async Task ConnectToDatabaseWithSpecificDatabase(string databaseName)
{ {
if (string.IsNullOrEmpty(selectedDatabaseCredential))
return;
isConnectingDatabase = true;
databaseErrorMessage = "";
try try
{ {
// Trova la credenziale
var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential);
if (credential == null)
{
databaseErrorMessage = "Credenziale database non trovata";
return;
}
// Test della connessione
var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name);
if (!success)
{
databaseErrorMessage = $"Connessione fallita: {message}";
return;
}
// Crea il database manager
Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential);
currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential);
Logger.LogInformation("Database manager creato con successo");
if (currentDatabaseManager == null) if (currentDatabaseManager == null)
{ {
databaseErrorMessage = "Database manager non disponibile"; databaseErrorMessage = "Database manager non disponibile";
Binary file not shown.
Binary file not shown.
Binary file not shown.
+53
View File
@@ -0,0 +1,53 @@
using CredentialManager.Data;
using CredentialManager.Models;
using CredentialManager.Services;
using Microsoft.EntityFrameworkCore;
Console.WriteLine("🧪 Testing SourceDatabaseName retrieval from credentials...");
// Configurazione del database temporaneo
var options = new DbContextOptionsBuilder<CredentialDbContext>()
.UseSqlite("Data Source=test_credential_db.db")
.Options;
using var context = new CredentialDbContext(options);
await context.Database.EnsureCreatedAsync();
var credentialService = new CredentialService(context);
// Test 1: Crea una credenziale database con nome database
var testCredential = new DatabaseCredential
{
Name = "TestDatabaseCredential",
DatabaseType = "SqlServer",
Host = "localhost",
Port = 1433,
DatabaseName = "MyProductionDB",
Username = "testuser",
Password = "testpassword"
};
Console.WriteLine($"📝 Creando credenziale con DatabaseName: {testCredential.DatabaseName}");
var credentialId = await credentialService.SaveDatabaseCredentialAsync(testCredential);
Console.WriteLine($"✅ Credenziale salvata con ID: {credentialId}");
// Test 2: Recupera la credenziale
var retrievedCredential = await credentialService.GetDatabaseCredentialAsync(credentialId);
Console.WriteLine($"✅ Credenziale recuperata: {retrievedCredential?.Name}");
Console.WriteLine($" DatabaseName: {retrievedCredential?.DatabaseName}");
// Test 3: Simula il recupero del database name come farebbe ProfileSaver
if (retrievedCredential != null && !string.IsNullOrEmpty(retrievedCredential.DatabaseName))
{
Console.WriteLine($"✅ SUCCESSO: DatabaseName recuperato dalle credenziali: {retrievedCredential.DatabaseName}");
}
else
{
Console.WriteLine("❌ ERRORE: DatabaseName non recuperato dalle credenziali");
}
// Pulizia
await context.Database.EnsureDeletedAsync();
Console.WriteLine("🧹 Database temporaneo eliminato");
Console.WriteLine("\n🎯 Test completato con successo!");
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
</ItemGroup>
</Project>
+62
View File
@@ -0,0 +1,62 @@
using CredentialManager.Data;
using CredentialManager.Models;
using CredentialManager.Services;
using Microsoft.EntityFrameworkCore;
Console.WriteLine("🧪 Testing SourceDatabaseName database persistence...");
// Configurazione del database temporaneo
var options = new DbContextOptionsBuilder<CredentialDbContext>()
.UseSqlite("Data Source=test_sourcedatabase.db")
.Options;
using var context = new CredentialDbContext(options);
await context.Database.EnsureCreatedAsync();
var profileService = new DataCouplerProfileService(context);
// Test: Creazione e salvataggio di un profilo con SourceDatabaseName
var testProfile = new DataCouplerProfile
{
Name = "Test Profile DB",
Description = "Test per verificare il salvataggio del SourceDatabaseName",
SourceType = "database",
SourceDatabaseName = "MyProductionDatabase",
SourceSchema = "dbo",
SourceTable = "customers",
DestinationType = "rest",
DestinationEndpoint = "/api/customers",
CreatedBy = "TestUser"
};
Console.WriteLine($"📝 Creando profilo con SourceDatabaseName: {testProfile.SourceDatabaseName}");
// Salvataggio nel database
var savedProfile = await profileService.SaveProfileAsync(testProfile);
Console.WriteLine($"✅ Profilo salvato con ID: {savedProfile.Id}");
// Recupero dal database
var retrievedProfile = await profileService.GetProfileByIdAsync(savedProfile.Id);
Console.WriteLine($"✅ Profilo recuperato dal database");
// Verifica che il SourceDatabaseName sia stato salvato e recuperato correttamente
if (retrievedProfile != null && retrievedProfile.SourceDatabaseName == testProfile.SourceDatabaseName)
{
Console.WriteLine($"✅ SUCCESSO: SourceDatabaseName salvato e recuperato correttamente: {retrievedProfile.SourceDatabaseName}");
}
else
{
Console.WriteLine($"❌ ERRORE: SourceDatabaseName non salvato correttamente");
Console.WriteLine($" Originale: {testProfile.SourceDatabaseName}");
Console.WriteLine($" Recuperato: {retrievedProfile?.SourceDatabaseName ?? "NULL"}");
}
// Test conversione DTO
var dto = profileService.ToDto(retrievedProfile!);
Console.WriteLine($"✅ DTO convertito con SourceDatabaseName: {dto.SourceDatabaseName}");
// Pulizia
await context.Database.EnsureDeletedAsync();
Console.WriteLine("🧹 Database temporaneo eliminato");
Console.WriteLine("\n🎯 Test completato con successo!");
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
</ItemGroup>
</Project>