From 77eb9943d0cbfc80d6f80a4bcf3e973c8f3303c2 Mon Sep 17 00:00:00 2001 From: Alberto Balbo Date: Sat, 24 Jan 2026 01:30:49 +0100 Subject: [PATCH] Gestione avanzata database e rimozione MaxClicks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aggiunta sezione impostazioni per manutenzione database (auto-salvataggio, pulizia duplicati/incompleti, retention, ottimizzazione). Implementati metodi asincroni in DatabaseService per pulizia e statistiche. Pulizia automatica all’avvio secondo impostazioni. Rimossa la proprietΓ  MaxClicks da modello, UI e logica. Migliorata la sicurezza thread-safe e la trasparenza nella gestione dati. Spostato il badge versione nelle info applicazione. --- Mimante/Pages/AuctionBrowser.razor | 1 - Mimante/Pages/Index.razor | 27 +- Mimante/Pages/Index.razor.cs | 44 ++-- Mimante/Pages/Settings.razor | 334 ++++++++++++++++++++++++ Mimante/Program.cs | 49 ++++ Mimante/Services/AuctionMonitor.cs | 12 +- Mimante/Services/BidooBrowserService.cs | 17 +- Mimante/Services/DatabaseService.cs | 148 ++++++++++- Mimante/Utilities/SettingsManager.cs | 29 ++ 9 files changed, 607 insertions(+), 54 deletions(-) diff --git a/Mimante/Pages/AuctionBrowser.razor b/Mimante/Pages/AuctionBrowser.razor index 2d45b1c..3c56bcd 100644 --- a/Mimante/Pages/AuctionBrowser.razor +++ b/Mimante/Pages/AuctionBrowser.razor @@ -547,7 +547,6 @@ private bool isUpdatingInBackground = false; CheckAuctionOpenBeforeBid = settings.DefaultCheckAuctionOpenBeforeBid, MinPrice = settings.DefaultMinPrice, MaxPrice = settings.DefaultMaxPrice, - MaxClicks = settings.DefaultMaxClicks, MinResets = settings.DefaultMinResets, MaxResets = settings.DefaultMaxResets, diff --git a/Mimante/Pages/Index.razor b/Mimante/Pages/Index.razor index 54b8322..be38020 100644 --- a/Mimante/Pages/Index.razor +++ b/Mimante/Pages/Index.razor @@ -237,14 +237,10 @@
-
+
-
- - -
@@ -396,7 +392,10 @@
- @if (selectedAuction.RecentBids != null && selectedAuction.RecentBids.Any()) + @{ + var recentBidsList = GetRecentBidsSafe(selectedAuction); + } + @if (recentBidsList.Any()) {
@@ -409,7 +408,7 @@ - @foreach (var bid in selectedAuction.RecentBids.Take(50)) + @foreach (var bid in recentBidsList.Take(50)) {
@@ -440,11 +439,12 @@
- @if (selectedAuction.RecentBids != null && selectedAuction.RecentBids.Any()) + @{ + // Crea una copia thread-safe per evitare modifiche durante l'enumerazione + var recentBidsCopy = GetRecentBidsSafe(selectedAuction); + } + @if (recentBidsCopy.Any()) { - // Crea una copia locale per evitare modifiche durante l'enumerazione - var recentBidsCopy = selectedAuction.RecentBids.ToList(); - // Calcola statistiche puntatori var bidderStats = recentBidsCopy .GroupBy(b => b.Username) @@ -574,8 +574,3 @@
} - - -
- v1.0.0 -
diff --git a/Mimante/Pages/Index.razor.cs b/Mimante/Pages/Index.razor.cs index baa4579..4ace39e 100644 --- a/Mimante/Pages/Index.razor.cs +++ b/Mimante/Pages/Index.razor.cs @@ -385,7 +385,6 @@ namespace AutoBidder.Pages CheckAuctionOpenBeforeBid = settings.DefaultCheckAuctionOpenBeforeBid, MinPrice = settings.DefaultMinPrice, MaxPrice = settings.DefaultMaxPrice, - MaxClicks = settings.DefaultMaxClicks, MinResets = settings.DefaultMinResets, MaxResets = settings.DefaultMaxResets, IsActive = isActive, @@ -748,22 +747,8 @@ namespace AutoBidder.Pages private string GetStatusAnimationClass(AuctionInfo auction) { - // Animazioni per stati speciali - if (auction.LastState != null) - { - switch (auction.LastState.Status) - { - case AuctionStatus.EndedWon: - return "status-anim-won"; - case AuctionStatus.Running when auction.IsAttackInProgress: - return "status-anim-attacking"; - } - } - - if (!auction.IsActive) return ""; - if (auction.IsPaused) return "status-anim-paused"; - if (auction.IsAttackInProgress) return "status-anim-attacking"; - return "status-anim-active"; + // Animazioni disabilitate - i colori sono sufficienti per identificare lo stato + return ""; } private string GetPriceDisplay(AuctionInfo? auction) @@ -990,6 +975,29 @@ namespace AutoBidder.Pages return auction.AuctionLog.TakeLast(50); } + /// + /// Ottiene una copia thread-safe della lista RecentBids + /// + private List GetRecentBidsSafe(AuctionInfo? auction) + { + if (auction?.RecentBids == null) + return new List(); + + try + { + // Lock per evitare modifiche durante la copia + lock (auction.RecentBids) + { + return auction.RecentBids.ToList(); + } + } + catch + { + // Fallback in caso di errore + return new List(); + } + } + private string GetLogEntryClass(LogEntry logEntry) { try @@ -1130,7 +1138,6 @@ namespace AutoBidder.Pages selectedAuction.MaxPrice = limits.MaxPrice; selectedAuction.MinResets = limits.MinResets; selectedAuction.MaxResets = limits.MaxResets; - selectedAuction.MaxClicks = limits.MaxBids; SaveAuctions(); @@ -1144,7 +1151,6 @@ namespace AutoBidder.Pages selectedAuction.MaxPrice = limits.MaxPrice; selectedAuction.MinResets = limits.MinResets; selectedAuction.MaxResets = limits.MaxResets; - selectedAuction.MaxClicks = limits.MaxBids; SaveAuctions(); diff --git a/Mimante/Pages/Settings.razor b/Mimante/Pages/Settings.razor index f4d0d84..f64ded2 100644 --- a/Mimante/Pages/Settings.razor +++ b/Mimante/Pages/Settings.razor @@ -208,6 +208,187 @@ + + +
+

+ +

+
+
+ +
Salvataggio Automatico
+
+
+
+ + +
+
+
+ + +
0 = mantieni tutto | 180 = 6 mesi (consigliato)
+
+
+ + +
Pulizia Automatica all'Avvio
+
+
+
+ + +
+
+
+
+ + +
+
+
+ + +
Manutenzione Manuale
+
+
+
+ Duplicati: +
@dbDuplicatesCount
+
+
+ Incompleti: +
@dbIncompleteCount
+
+
+ +
+
+
+ +
+ + + + +
+ + @if (!string.IsNullOrEmpty(dbCleanupMessage)) + { +
+ + @dbCleanupMessage +
+ } + +
+ +
+
+
+
+ + +
+

+ +

+
+
+
+
+
+
+
Versione
+
+ + v1.0.0 +
+
+
+
+
+
+
+
Ambiente
+
+ + .NET 8 +
+
+
+
+
+
+
+
Informazioni Sistema
+
+
+ Database: +
+ @if (DbService.IsAvailable) + { + Operativo + } + else + { + Non disponibile + } +
+
+
+ Sessione: +
+ @if (!string.IsNullOrEmpty(currentUsername)) + { + @currentUsername + } + else + { + Non connesso + } +
+
+
+
+
+
+
+
+
+
@@ -423,6 +604,159 @@ private System.Threading.Timer? updateTimer; return "bg-success"; } + // ??????????????????????????????????????????????????????????????? + // DATABASE MANAGEMENT + // ??????????????????????????????????????????????????????????????? + + [Inject] private DatabaseService DbService { get; set; } = default!; + + private int dbDuplicatesCount = 0; + private int dbIncompleteCount = 0; + private bool isLoadingDbStats = false; + private bool isCleaningDb = false; + private string? dbCleanupMessage = null; + private bool dbCleanupSuccess = false; + + private async Task RefreshDbStats() + { + if (!DbService.IsAvailable) return; + + isLoadingDbStats = true; + dbCleanupMessage = null; + StateHasChanged(); + + try + { + dbDuplicatesCount = await DbService.CountDuplicateAuctionResultsAsync(); + dbIncompleteCount = await DbService.CountIncompleteAuctionResultsAsync(); + } + catch (Exception ex) + { + dbCleanupMessage = $"Errore: {ex.Message}"; + dbCleanupSuccess = false; + } + finally + { + isLoadingDbStats = false; + StateHasChanged(); + } + } + + private async Task CleanupDuplicates() + { + if (!DbService.IsAvailable) return; + + isCleaningDb = true; + dbCleanupMessage = null; + StateHasChanged(); + + try + { + var removed = await DbService.RemoveDuplicateAuctionResultsAsync(); + dbCleanupMessage = $"? Rimossi {removed} record duplicati"; + dbCleanupSuccess = true; + await RefreshDbStats(); + } + catch (Exception ex) + { + dbCleanupMessage = $"Errore: {ex.Message}"; + dbCleanupSuccess = false; + } + finally + { + isCleaningDb = false; + StateHasChanged(); + } + } + + private async Task CleanupIncomplete() + { + if (!DbService.IsAvailable) return; + + isCleaningDb = true; + dbCleanupMessage = null; + StateHasChanged(); + + try + { + var removed = await DbService.RemoveIncompleteAuctionResultsAsync(); + dbCleanupMessage = $"? Rimossi {removed} record incompleti"; + dbCleanupSuccess = true; + await RefreshDbStats(); + } + catch (Exception ex) + { + dbCleanupMessage = $"Errore: {ex.Message}"; + dbCleanupSuccess = false; + } + finally + { + isCleaningDb = false; + StateHasChanged(); + } + } + + private async Task CleanupDatabase() + { + if (!DbService.IsAvailable) return; + + isCleaningDb = true; + dbCleanupMessage = null; + StateHasChanged(); + + try + { + var message = await DbService.CleanupDatabaseAsync(); + dbCleanupMessage = $"? {message}"; + dbCleanupSuccess = true; + await RefreshDbStats(); + } + catch (Exception ex) + { + dbCleanupMessage = $"Errore: {ex.Message}"; + dbCleanupSuccess = false; + } + finally + { + isCleaningDb = false; + StateHasChanged(); + } + } + + private async Task OptimizeDatabase() + { + if (!DbService.IsAvailable) return; + + isCleaningDb = true; + dbCleanupMessage = null; + StateHasChanged(); + + try + { + await DbService.OptimizeDatabaseAsync(); + dbCleanupMessage = "? Database ottimizzato (VACUUM eseguito)"; + dbCleanupSuccess = true; + } + catch (Exception ex) + { + dbCleanupMessage = $"Errore: {ex.Message}"; + dbCleanupSuccess = false; + } + finally + { + isCleaningDb = false; + StateHasChanged(); + } + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender && DbService.IsAvailable) + { + await RefreshDbStats(); + } + } + public void Dispose() { updateTimer?.Dispose(); diff --git a/Mimante/Program.cs b/Mimante/Program.cs index 1c49bee..63db6c3 100644 --- a/Mimante/Program.cs +++ b/Mimante/Program.cs @@ -277,6 +277,55 @@ using (var scope = app.Services.CreateScope()) var isHealthy = await databaseService.CheckDatabaseHealthAsync(); Console.WriteLine($"[DB] Database health check: {(isHealthy ? "OK" : "FAILED")}"); + // πŸ”₯ MANUTENZIONE AUTOMATICA DATABASE + var settings = AutoBidder.Utilities.SettingsManager.Load(); + + if (settings.DatabaseAutoCleanupDuplicates) + { + Console.WriteLine("[DB] Checking for duplicate records..."); + var duplicateCount = await databaseService.CountDuplicateAuctionResultsAsync(); + if (duplicateCount > 0) + { + Console.WriteLine($"[DB] Found {duplicateCount} duplicates - removing..."); + var removed = await databaseService.RemoveDuplicateAuctionResultsAsync(); + Console.WriteLine($"[DB] βœ“ Removed {removed} duplicate auction results"); + } + else + { + Console.WriteLine("[DB] βœ“ No duplicates found"); + } + } + + if (settings.DatabaseAutoCleanupIncomplete) + { + Console.WriteLine("[DB] Checking for incomplete records..."); + var incompleteCount = await databaseService.CountIncompleteAuctionResultsAsync(); + if (incompleteCount > 0) + { + Console.WriteLine($"[DB] Found {incompleteCount} incomplete records - removing..."); + var removed = await databaseService.RemoveIncompleteAuctionResultsAsync(); + Console.WriteLine($"[DB] βœ“ Removed {removed} incomplete auction results"); + } + else + { + Console.WriteLine("[DB] βœ“ No incomplete records found"); + } + } + + if (settings.DatabaseMaxRetentionDays > 0) + { + Console.WriteLine($"[DB] Checking for records older than {settings.DatabaseMaxRetentionDays} days..."); + var oldCount = await databaseService.RemoveOldAuctionResultsAsync(settings.DatabaseMaxRetentionDays); + if (oldCount > 0) + { + Console.WriteLine($"[DB] βœ“ Removed {oldCount} old auction results"); + } + else + { + Console.WriteLine($"[DB] βœ“ No old records to remove"); + } + } + // πŸ†• Esegui diagnostica completa se ci sono problemi o se richiesto var runDiagnostics = Environment.GetEnvironmentVariable("DB_DIAGNOSTICS")?.ToLower() == "true"; if (!isHealthy || runDiagnostics) diff --git a/Mimante/Services/AuctionMonitor.cs b/Mimante/Services/AuctionMonitor.cs index b921b3f..3638448 100644 --- a/Mimante/Services/AuctionMonitor.cs +++ b/Mimante/Services/AuctionMonitor.cs @@ -176,9 +176,8 @@ namespace AutoBidder.Services auction.MaxPrice = maxPrice; auction.MinResets = minResets; auction.MaxResets = maxResets; - auction.MaxClicks = maxBids; - OnLog?.Invoke($"[LIMITS] Aggiornati limiti per {auction.Name}: MinPrice={minPrice:F2}, MaxPrice={maxPrice:F2}, MinResets={minResets}, MaxResets={maxResets}, MaxBids={maxBids}"); + OnLog?.Invoke($"[LIMITS] Aggiornati limiti per {auction.Name}: MinPrice={minPrice:F2}, MaxPrice={maxPrice:F2}, MinResets={minResets}, MaxResets={maxResets}"); return true; } } @@ -202,7 +201,6 @@ namespace AutoBidder.Services auction.MaxPrice = maxPrice; auction.MinResets = minResets; auction.MaxResets = maxResets; - auction.MaxClicks = maxBids; count++; OnLog?.Invoke($"[LIMITS] Aggiornati limiti per {auction.Name}"); @@ -653,14 +651,6 @@ namespace AutoBidder.Services return false; } - // ??? CONTROLLO 5: MaxClicks - int myBidsCount = auction.BidHistory.Count(b => b.EventType == BidEventType.MyBid); - if (auction.MaxClicks > 0 && myBidsCount >= auction.MaxClicks) - { - auction.AddLog($"[CLICKS] Click massimi raggiunti: {myBidsCount} >= Max {auction.MaxClicks}"); - return false; - } - // ?? CONTROLLO 6: Cooldown (evita puntate multiple ravvicinate) if (auction.LastClickAt.HasValue) { diff --git a/Mimante/Services/BidooBrowserService.cs b/Mimante/Services/BidooBrowserService.cs index 9a5108a..1e13150 100644 --- a/Mimante/Services/BidooBrowserService.cs +++ b/Mimante/Services/BidooBrowserService.cs @@ -219,15 +219,20 @@ namespace AutoBidder.Services // Parse aste dall'HTML (fragment AJAX) auctions = ParseAuctionsFromHtml(html); - // ?? FIX: Filtra solo aste di puntate se categoria "Aste di Puntate" (TabId = 1) + Console.WriteLine($"[BidooBrowser] Trovate {auctions.Count} aste nella categoria {category.DisplayName}"); + + // ?? DEBUG: Verifica quante aste hanno IsCreditAuction = true if (category.IsSpecialCategory && category.TabId == 1) { - var before = auctions.Count; - auctions = auctions.Where(a => a.IsCreditAuction).ToList(); - Console.WriteLine($"[BidooBrowser] Filtrate {before} -> {auctions.Count} aste di puntate (IsCreditAuction = true)"); + var creditCount = auctions.Count(a => a.IsCreditAuction); + Console.WriteLine($"[BidooBrowser] DEBUG Aste di Puntate: {creditCount}/{auctions.Count} hanno IsCreditAuction=true"); + + // Log primi 3 nomi per debug + foreach (var a in auctions.Take(3)) + { + Console.WriteLine($"[BidooBrowser] - {a.Name} (ID: {a.AuctionId}, IsCreditAuction: {a.IsCreditAuction})"); + } } - - Console.WriteLine($"[BidooBrowser] Trovate {auctions.Count} aste nella categoria {category.DisplayName}"); } catch (Exception ex) { diff --git a/Mimante/Services/DatabaseService.cs b/Mimante/Services/DatabaseService.cs index da13c5a..9850ca1 100644 --- a/Mimante/Services/DatabaseService.cs +++ b/Mimante/Services/DatabaseService.cs @@ -1037,6 +1037,7 @@ namespace AutoBidder.Services /// /// Salva risultato asta con dati completi per analytics + /// πŸ”₯ ANTI-DUPLICAZIONE: Usa UPSERT basato su AuctionId per evitare duplicati /// public async Task SaveAuctionResultAsync(string auctionId, string auctionName, double finalPrice, int bidsUsed, bool won, double? buyNowPrice = null, double? shippingCost = null, double? totalCost = null, double? savings = null, @@ -1044,6 +1045,22 @@ namespace AutoBidder.Services { var closedAtHour = DateTime.UtcNow.Hour; + // πŸ”₯ FIX DUPLICAZIONE: Prima controlla se esiste giΓ  + var checkSql = "SELECT COUNT(*) FROM AuctionResults WHERE AuctionId = @auctionId;"; + + await using var checkConn = await GetConnectionAsync(); + await using var checkCmd = checkConn.CreateCommand(); + checkCmd.CommandText = checkSql; + checkCmd.Parameters.AddWithValue("@auctionId", auctionId); + var countResult = await checkCmd.ExecuteScalarAsync(); + var exists = Convert.ToInt64(countResult ?? 0) > 0; + + if (exists) + { + Console.WriteLine($"[DatabaseService] ⚠ Asta {auctionId} giΓ  presente in AuctionResults - Skipping INSERT to prevent duplicate"); + return; + } + var sql = @" INSERT INTO AuctionResults (AuctionId, AuctionName, FinalPrice, BidsUsed, Won, BuyNowPrice, ShippingCost, TotalCost, Savings, Timestamp, @@ -1368,6 +1385,136 @@ namespace AutoBidder.Services return results; } + // ═══════════════════════════════════════════════════════════════════ + // MANUTENZIONE DATABASE + // ═══════════════════════════════════════════════════════════════════ + + /// + /// Rimuove record duplicati dalla tabella AuctionResults + /// Mantiene solo il record piΓΉ recente per ogni AuctionId + /// + public async Task RemoveDuplicateAuctionResultsAsync() + { + var sql = @" + DELETE FROM AuctionResults + WHERE Id NOT IN ( + SELECT MAX(Id) + FROM AuctionResults + GROUP BY AuctionId + ); + "; + + await using var connection = await GetConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + var deletedRows = await cmd.ExecuteNonQueryAsync(); + + Console.WriteLine($"[DatabaseService] Rimossi {deletedRows} record duplicati da AuctionResults"); + return deletedRows; + } + + /// + /// Rimuove aste con dati incompleti o invalidi + /// + public async Task RemoveIncompleteAuctionResultsAsync() + { + var sql = @" + DELETE FROM AuctionResults + WHERE + AuctionId IS NULL OR AuctionId = '' OR + AuctionName IS NULL OR AuctionName = '' OR + FinalPrice < 0 OR FinalPrice > 10000 OR + BidsUsed < 0 OR BidsUsed > 50000 OR + (BuyNowPrice IS NOT NULL AND BuyNowPrice <= 0) OR + (TotalCost IS NOT NULL AND TotalCost < 0) OR + (Savings IS NOT NULL AND Savings < -1000 OR Savings > 1000); + "; + + await using var connection = await GetConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + var deletedRows = await cmd.ExecuteNonQueryAsync(); + + Console.WriteLine($"[DatabaseService] Rimossi {deletedRows} record con dati incompleti/invalidi"); + return deletedRows; + } + + /// + /// Esegue pulizia completa del database (duplicati + incompleti + VACUUM) + /// + public async Task CleanupDatabaseAsync() + { + var duplicates = await RemoveDuplicateAuctionResultsAsync(); + var incomplete = await RemoveIncompleteAuctionResultsAsync(); + + // VACUUM per recuperare spazio + await OptimizeDatabaseAsync(); + + return $"Rimossi {duplicates} duplicati e {incomplete} record incompleti. Database ottimizzato."; + } + + /// + /// Conta i record duplicati senza rimuoverli + /// + public async Task CountDuplicateAuctionResultsAsync() + { + var sql = @" + SELECT COUNT(*) - COUNT(DISTINCT AuctionId) + FROM AuctionResults; + "; + + await using var connection = await GetConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + var result = await cmd.ExecuteScalarAsync(); + return Convert.ToInt32(result ?? 0); + } + + /// + /// Conta record con dati incompleti + /// + public async Task CountIncompleteAuctionResultsAsync() + { + var sql = @" + SELECT COUNT(*) + FROM AuctionResults + WHERE + AuctionId IS NULL OR AuctionId = '' OR + AuctionName IS NULL OR AuctionName = '' OR + FinalPrice < 0 OR FinalPrice > 10000 OR + BidsUsed < 0 OR BidsUsed > 50000 OR + (BuyNowPrice IS NOT NULL AND BuyNowPrice <= 0) OR + (TotalCost IS NOT NULL AND TotalCost < 0) OR + (Savings IS NOT NULL AND Savings < -1000 OR Savings > 1000); + "; + + await using var connection = await GetConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + var result = await cmd.ExecuteScalarAsync(); + return Convert.ToInt32(result ?? 0); + } + + /// + /// Rimuove record piΓΉ vecchi di N giorni + /// + public async Task RemoveOldAuctionResultsAsync(int daysToKeep) + { + if (daysToKeep <= 0) return 0; + + var cutoffDate = DateTime.UtcNow.AddDays(-daysToKeep).ToString("O"); + var sql = "DELETE FROM AuctionResults WHERE Timestamp < @cutoffDate;"; + + await using var connection = await GetConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = sql; + cmd.Parameters.AddWithValue("@cutoffDate", cutoffDate); + var deletedRows = await cmd.ExecuteNonQueryAsync(); + + Console.WriteLine($"[DatabaseService] Rimossi {deletedRows} record piΓΉ vecchi di {daysToKeep} giorni"); + return deletedRows; + } + public void Dispose() { // Non ci sono risorse da rilasciare - le connessioni sono gestite con using @@ -1381,7 +1528,6 @@ namespace AutoBidder.Services public int Version { get; } public string Description { get; } private readonly Func _execute; - private string? _lastSqlExecuted; public Migration(int version, string description, Func execute) { diff --git a/Mimante/Utilities/SettingsManager.cs b/Mimante/Utilities/SettingsManager.cs index 00e6790..c91419e 100644 --- a/Mimante/Utilities/SettingsManager.cs +++ b/Mimante/Utilities/SettingsManager.cs @@ -70,6 +70,35 @@ namespace AutoBidder.Utilities /// Default: "Normal" (uso giornaliero - errori e warning) /// public string MinLogLevel { get; set; } = "Normal"; + + // ??????????????????????????????????????????????????????????????? + // IMPOSTAZIONI DATABASE + // ??????????????????????????????????????????????????????????????? + + /// + /// Abilita il salvataggio automatico delle aste completate nel database. + /// Default: true (consigliato per statistiche) + /// + public bool DatabaseAutoSaveEnabled { get; set; } = true; + + /// + /// Esegue pulizia automatica duplicati all'avvio dell'applicazione. + /// Default: true (consigliato per mantenere database pulito) + /// + public bool DatabaseAutoCleanupDuplicates { get; set; } = true; + + /// + /// Esegue pulizia automatica record incompleti all'avvio. + /// Default: false (puς rimuovere dati utili in caso di errori temporanei) + /// + public bool DatabaseAutoCleanupIncomplete { get; set; } = false; + + /// + /// Numero massimo di giorni da mantenere nei risultati aste. + /// Record piω vecchi vengono eliminati automaticamente. + /// Default: 180 (6 mesi), 0 = disabilitato + /// + public int DatabaseMaxRetentionDays { get; set; } = 180; } public static class SettingsManager