// Copyright (C) 2021-present sam (starshines.gay) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using Catalogger.Backend.Database.Models; using Catalogger.Backend.Extensions; using EntityFramework.Exceptions.PostgreSQL; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql; namespace Catalogger.Backend.Database; public class DatabaseContext : DbContext { private readonly NpgsqlDataSource _dataSource; private readonly ILoggerFactory? _loggerFactory; public DbSet Guilds { get; set; } public DbSet Messages { get; set; } public DbSet IgnoredMessages { get; set; } public DbSet Invites { get; set; } public DbSet Watchlists { get; set; } public DbSet ApiTokens { get; set; } public DatabaseContext(Config config, ILoggerFactory? loggerFactory) { var connString = new NpgsqlConnectionStringBuilder(config.Database.Url) { Timeout = config.Database.Timeout ?? 5, MaxPoolSize = config.Database.MaxPoolSize ?? 50, }.ConnectionString; var dataSourceBuilder = new NpgsqlDataSourceBuilder(connString); dataSourceBuilder.EnableDynamicJson().UseNodaTime(); _dataSource = dataSourceBuilder.Build(); _loggerFactory = loggerFactory; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .ConfigureWarnings(c => c.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning) .Ignore(CoreEventId.SaveChangesFailed) ) .UseNpgsql(_dataSource, o => o.UseNodaTime()) .UseSnakeCaseNamingConvention() .UseLoggerFactory(_loggerFactory) .UseExceptionProcessor(); protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) { configurationBuilder.Properties().HaveConversion(); configurationBuilder.Properties>().HaveConversion(); } private static readonly ValueComparer> UlongListValueComparer = new( (c1, c2) => c1 != null && c2 != null && c1.SequenceEqual(c2), c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())) ); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder .Entity() .Property(g => g.KeyRoles) .Metadata.SetValueComparer(UlongListValueComparer); modelBuilder.Entity().HasKey(i => i.Code); modelBuilder.Entity().HasIndex(i => i.GuildId); modelBuilder.Entity().HasKey(w => new { w.GuildId, w.UserId }); modelBuilder.Entity().Property(w => w.AddedAt).HasDefaultValueSql("now()"); } } public class DesignTimeDatabaseContextFactory : IDesignTimeDbContextFactory { public DatabaseContext CreateDbContext(string[] args) { // Read the configuration file var config = new ConfigurationBuilder() .AddConfiguration() .Build() // Get the configuration as our config class .Get() ?? new(); return new DatabaseContext(config, null); } } public class UlongValueConverter() : ValueConverter( convertToProviderExpression: x => (long)x, convertFromProviderExpression: x => (ulong)x ); public class UlongArrayValueConverter() : ValueConverter, List>( convertToProviderExpression: x => x.Select(i => (long)i).ToList(), convertFromProviderExpression: x => x.Select(i => (ulong)i).ToList() );