61f0945db2
Aggiornamento massivo: aggiunto backend PostgreSQL per statistiche aste con fallback SQLite, nuovi modelli e servizi, UI moderna con grafici interattivi, refactoring stato applicazione (ApplicationStateService), documentazione completa per deploy Docker/Unraid/Gitea, nuovi CSS e script JS per UX avanzata, template Unraid, test database, e workflow CI/CD estesi. Pronto per produzione e analisi avanzate.
212 lines
12 KiB
C#
212 lines
12 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using AutoBidder.Models;
|
|
|
|
namespace AutoBidder.Data
|
|
{
|
|
/// <summary>
|
|
/// Context Entity Framework per PostgreSQL - Database Statistiche Aste
|
|
/// Gestisce aste concluse, metriche strategiche e analisi performance
|
|
/// </summary>
|
|
public class PostgresStatsContext : DbContext
|
|
{
|
|
public PostgresStatsContext(DbContextOptions<PostgresStatsContext> options)
|
|
: base(options)
|
|
{
|
|
}
|
|
|
|
// Tabelle principali
|
|
public DbSet<CompletedAuction> CompletedAuctions { get; set; }
|
|
public DbSet<BidderPerformance> BidderPerformances { get; set; }
|
|
public DbSet<ProductStatistic> ProductStatistics { get; set; }
|
|
public DbSet<DailyMetric> DailyMetrics { get; set; }
|
|
public DbSet<StrategicInsight> StrategicInsights { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
// Configurazione CompletedAuction
|
|
modelBuilder.Entity<CompletedAuction>(entity =>
|
|
{
|
|
entity.ToTable("completed_auctions");
|
|
entity.HasKey(e => e.Id);
|
|
|
|
entity.Property(e => e.Id).HasColumnName("id");
|
|
entity.Property(e => e.AuctionId).HasColumnName("auction_id").IsRequired().HasMaxLength(100);
|
|
entity.Property(e => e.ProductName).HasColumnName("product_name").IsRequired().HasMaxLength(500);
|
|
entity.Property(e => e.FinalPrice).HasColumnName("final_price").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.BuyNowPrice).HasColumnName("buy_now_price").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.ShippingCost).HasColumnName("shipping_cost").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.TotalBids).HasColumnName("total_bids");
|
|
entity.Property(e => e.MyBidsCount).HasColumnName("my_bids_count");
|
|
entity.Property(e => e.ResetCount).HasColumnName("reset_count");
|
|
entity.Property(e => e.Won).HasColumnName("won");
|
|
entity.Property(e => e.WinnerUsername).HasColumnName("winner_username").HasMaxLength(100);
|
|
entity.Property(e => e.CompletedAt).HasColumnName("completed_at");
|
|
entity.Property(e => e.DurationSeconds).HasColumnName("duration_seconds");
|
|
entity.Property(e => e.AverageLatency).HasColumnName("average_latency").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.Savings).HasColumnName("savings").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.TotalCost).HasColumnName("total_cost").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("CURRENT_TIMESTAMP");
|
|
|
|
entity.HasIndex(e => e.AuctionId).HasDatabaseName("idx_auction_id");
|
|
entity.HasIndex(e => e.ProductName).HasDatabaseName("idx_product_name");
|
|
entity.HasIndex(e => e.CompletedAt).HasDatabaseName("idx_completed_at");
|
|
entity.HasIndex(e => e.Won).HasDatabaseName("idx_won");
|
|
});
|
|
|
|
// Configurazione BidderPerformance
|
|
modelBuilder.Entity<BidderPerformance>(entity =>
|
|
{
|
|
entity.ToTable("bidder_performances");
|
|
entity.HasKey(e => e.Id);
|
|
|
|
entity.Property(e => e.Id).HasColumnName("id");
|
|
entity.Property(e => e.Username).HasColumnName("username").IsRequired().HasMaxLength(100);
|
|
entity.Property(e => e.TotalAuctions).HasColumnName("total_auctions");
|
|
entity.Property(e => e.AuctionsWon).HasColumnName("auctions_won");
|
|
entity.Property(e => e.AuctionsLost).HasColumnName("auctions_lost");
|
|
entity.Property(e => e.TotalBidsPlaced).HasColumnName("total_bids_placed");
|
|
entity.Property(e => e.WinRate).HasColumnName("win_rate").HasColumnType("decimal(5,2)");
|
|
entity.Property(e => e.AverageBidsPerAuction).HasColumnName("average_bids_per_auction").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.AverageCompetition).HasColumnName("average_competition").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.IsAggressive).HasColumnName("is_aggressive");
|
|
entity.Property(e => e.LastSeenAt).HasColumnName("last_seen_at");
|
|
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at").HasDefaultValueSql("CURRENT_TIMESTAMP");
|
|
|
|
entity.HasIndex(e => e.Username).IsUnique().HasDatabaseName("idx_username");
|
|
entity.HasIndex(e => e.WinRate).HasDatabaseName("idx_win_rate");
|
|
});
|
|
|
|
// Configurazione ProductStatistic
|
|
modelBuilder.Entity<ProductStatistic>(entity =>
|
|
{
|
|
entity.ToTable("product_statistics");
|
|
entity.HasKey(e => e.Id);
|
|
|
|
entity.Property(e => e.Id).HasColumnName("id");
|
|
entity.Property(e => e.ProductKey).HasColumnName("product_key").IsRequired().HasMaxLength(200);
|
|
entity.Property(e => e.ProductName).HasColumnName("product_name").IsRequired().HasMaxLength(500);
|
|
entity.Property(e => e.TotalAuctions).HasColumnName("total_auctions");
|
|
entity.Property(e => e.AverageWinningBids).HasColumnName("average_winning_bids").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.AverageFinalPrice).HasColumnName("average_final_price").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.AverageResets).HasColumnName("average_resets").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.MinBidsSeen).HasColumnName("min_bids_seen");
|
|
entity.Property(e => e.MaxBidsSeen).HasColumnName("max_bids_seen");
|
|
entity.Property(e => e.RecommendedMaxBids).HasColumnName("recommended_max_bids");
|
|
entity.Property(e => e.RecommendedMaxPrice).HasColumnName("recommended_max_price").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.CompetitionLevel).HasColumnName("competition_level").HasMaxLength(20);
|
|
entity.Property(e => e.LastUpdated).HasColumnName("last_updated").HasDefaultValueSql("CURRENT_TIMESTAMP");
|
|
|
|
entity.HasIndex(e => e.ProductKey).IsUnique().HasDatabaseName("idx_product_key");
|
|
entity.HasIndex(e => e.ProductName).HasDatabaseName("idx_product_name_stats");
|
|
});
|
|
|
|
// Configurazione DailyMetric
|
|
modelBuilder.Entity<DailyMetric>(entity =>
|
|
{
|
|
entity.ToTable("daily_metrics");
|
|
entity.HasKey(e => e.Id);
|
|
|
|
entity.Property(e => e.Id).HasColumnName("id");
|
|
entity.Property(e => e.Date).HasColumnName("date").HasColumnType("date");
|
|
entity.Property(e => e.TotalBidsUsed).HasColumnName("total_bids_used");
|
|
entity.Property(e => e.MoneySpent).HasColumnName("money_spent").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.AuctionsWon).HasColumnName("auctions_won");
|
|
entity.Property(e => e.AuctionsLost).HasColumnName("auctions_lost");
|
|
entity.Property(e => e.TotalSavings).HasColumnName("total_savings").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.AverageLatency).HasColumnName("average_latency").HasColumnType("decimal(10,2)");
|
|
entity.Property(e => e.WinRate).HasColumnName("win_rate").HasColumnType("decimal(5,2)");
|
|
entity.Property(e => e.ROI).HasColumnName("roi").HasColumnType("decimal(10,2)");
|
|
|
|
entity.HasIndex(e => e.Date).IsUnique().HasDatabaseName("idx_date");
|
|
});
|
|
|
|
// Configurazione StrategicInsight
|
|
modelBuilder.Entity<StrategicInsight>(entity =>
|
|
{
|
|
entity.ToTable("strategic_insights");
|
|
entity.HasKey(e => e.Id);
|
|
|
|
entity.Property(e => e.Id).HasColumnName("id");
|
|
entity.Property(e => e.InsightType).HasColumnName("insight_type").IsRequired().HasMaxLength(50);
|
|
entity.Property(e => e.ProductKey).HasColumnName("product_key").HasMaxLength(200);
|
|
entity.Property(e => e.RecommendedAction).HasColumnName("recommended_action").IsRequired();
|
|
entity.Property(e => e.ConfidenceLevel).HasColumnName("confidence_level").HasColumnType("decimal(5,2)");
|
|
entity.Property(e => e.DataPoints).HasColumnName("data_points");
|
|
entity.Property(e => e.Reasoning).HasColumnName("reasoning");
|
|
entity.Property(e => e.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("CURRENT_TIMESTAMP");
|
|
entity.Property(e => e.IsActive).HasColumnName("is_active").HasDefaultValue(true);
|
|
|
|
entity.HasIndex(e => e.InsightType).HasDatabaseName("idx_insight_type");
|
|
entity.HasIndex(e => e.ProductKey).HasDatabaseName("idx_product_key_insight");
|
|
entity.HasIndex(e => e.CreatedAt).HasDatabaseName("idx_created_at");
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica e crea lo schema del database
|
|
/// </summary>
|
|
public async Task<bool> EnsureSchemaAsync()
|
|
{
|
|
try
|
|
{
|
|
// Verifica connessione
|
|
if (!await Database.CanConnectAsync())
|
|
{
|
|
Console.WriteLine("[PostgreSQL] Cannot connect to database");
|
|
return false;
|
|
}
|
|
|
|
// Crea schema se non esistono le tabelle (senza migrations)
|
|
var created = await Database.EnsureCreatedAsync();
|
|
|
|
if (created)
|
|
{
|
|
Console.WriteLine("[PostgreSQL] Schema created successfully");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("[PostgreSQL] Schema already exists");
|
|
}
|
|
|
|
// Verifica che tutte le tabelle esistano
|
|
var hasCompletedAuctions = await CompletedAuctions.AnyAsync();
|
|
Console.WriteLine($"[PostgreSQL] Database verified - {(hasCompletedAuctions ? "has data" : "empty")}");
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[PostgreSQL ERROR] Schema creation failed: {ex.Message}");
|
|
Console.WriteLine($"[PostgreSQL ERROR] Stack trace: {ex.StackTrace}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica che tutte le tabelle richieste esistano
|
|
/// </summary>
|
|
public async Task<bool> ValidateSchemaAsync()
|
|
{
|
|
try
|
|
{
|
|
// Prova a contare le righe di ogni tabella (forza check esistenza)
|
|
await CompletedAuctions.CountAsync();
|
|
await BidderPerformances.CountAsync();
|
|
await ProductStatistics.CountAsync();
|
|
await DailyMetrics.CountAsync();
|
|
await StrategicInsights.CountAsync();
|
|
|
|
Console.WriteLine("[PostgreSQL] All tables validated successfully");
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[PostgreSQL ERROR] Schema validation failed: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|