using ErrorOr; using MassTransit; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Musify.Application.Contracts; using Musify.Domain.Entities; using Musify.Domain.ValueObjects; using Musify.Infrastructure.Configuration; using Npgsql; namespace Musify.Infrastructure.Persistence; public class Database(DatabaseConfiguration configuration) : DbContext, Musify.Application.Contracts.IDatabase { private static readonly AuditableEntityInterceptor AuditInterceptor = new(); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseNpgsql(configuration.ConnectionString, o => o.MapEnum("genre")) .AddInterceptors(AuditInterceptor); } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.AddInboxStateEntity(); modelBuilder.AddOutboxMessageEntity(); modelBuilder.AddOutboxStateEntity(); ModelConfiguration.Apply(modelBuilder); } public DbSet Users => Set(); public DbSet Tracks => Set(); public DbSet PlayLists => Set(); public DbSet Albums => Set(); public DbSet AlbumHasTracks => Set(); public DbSet PlayListHasTracks => Set(); public DbSet Mixes => Set(); public DbSet MixItems => Set(); public DbSet UploadIntents => Set(); public DbSet ListeningHistories => Set(); public DbSet TrackLikes => Set(); public DbSet TrackTags => Set(); public DbSet UserFollows => Set(); public async Task BeginTransactionAsync(System.Data.IsolationLevel isolationLevel, CancellationToken cancellationToken) { var transaction = await Database.BeginTransactionAsync(isolationLevel, cancellationToken); return new DatabaseTransaction(transaction); } public async Task> TrySaveChangesAsync(Error onUniqueViolation, CancellationToken cancellationToken) { try { await SaveChangesAsync(cancellationToken); return Result.Success; } catch (DbUpdateException exception) when (exception.InnerException is PostgresException { SqlState: PostgresErrorCodes.UniqueViolation }) { return onUniqueViolation; } } private sealed class DatabaseTransaction(IDbContextTransaction inner) : IDatabaseTransaction { public Task CommitAsync(CancellationToken cancellationToken) => inner.CommitAsync(cancellationToken); public ValueTask DisposeAsync() => inner.DisposeAsync(); } }