using Extrudex.Domain.Base; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Extrudex.Infrastructure.Data.Configurations; /// /// Base configuration for all entities. Sets up common conventions: /// - Table names in snake_case /// - GUID primary keys stored as PostgreSQL UUID /// - Automatic timestamp columns in snake_case /// /// The entity type to configure. public abstract class BaseEntityConfiguration : IEntityTypeConfiguration where TEntity : BaseEntity { public virtual void Configure(EntityTypeBuilder builder) { // Table name in snake_case builder.ToTable(ToSnakeCase(typeof(TEntity).Name)); // Primary key stored as UUID builder.HasKey(e => e.Id); builder.Property(e => e.Id) .HasColumnName("id") .ValueGeneratedNever(); // If the entity is auditable, configure the timestamp columns if (typeof(AuditableEntity).IsAssignableFrom(typeof(TEntity))) { ConfigureAuditColumns(builder); } } /// /// Configures audit timestamp columns (created_at, updated_at) for auditable entities. /// Uses string-based property names since the generic type constraint is BaseEntity /// and cannot be cast to AuditableEntity at compile time. /// private static void ConfigureAuditColumns(EntityTypeBuilder builder) { builder.Property("CreatedAt") .HasColumnName("created_at") .HasDefaultValueSql("now() at time zone 'utc'"); builder.Property("UpdatedAt") .HasColumnName("updated_at") .HasDefaultValueSql("now() at time zone 'utc'"); } /// /// Converts PascalCase or camelCase entity name to plural snake_case table name. /// e.g. MaterialBase → material_bases, AmsSlot → ams_slots /// protected static string ToSnakeCase(string name) { var snake = string.Concat( name.Select((ch, i) => i > 0 && char.IsUpper(ch) && (char.IsLower(name[i - 1]) || (i + 1 < name.Length && char.IsLower(name[i + 1]))) ? "_" + ch : ch.ToString())) .ToLowerInvariant(); // Pluralize: add 's' (handles most cases; irregular plurals handled explicitly if needed) // Special cases: already_plural stays, 'y' → 'ies', 's'/'x'/'ch'/'sh' → 'es' if (snake.EndsWith("s")) return snake; // Already plural or ambiguous — leave as-is if (snake.EndsWith("y") && !snake.EndsWith("ay") && !snake.EndsWith("ey") && !snake.EndsWith("oy") && !snake.EndsWith("uy")) return snake[..^1] + "ies"; if (snake.EndsWith("x") || snake.EndsWith("ch") || snake.EndsWith("sh")) return snake + "es"; return snake + "s"; } }