using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Keyapis.EF.DataAnnotations; using Keyapis.EF.Extensions; using Keyapis.EF.ValueConverters; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; // ReSharper disable once CheckNamespace namespace Keyapis.EF.Jwks.V1.Data.JwksDb; public interface IJwksDbContext : IAsyncDisposable, IDisposable { public DbSet KeyInfos { get; set; } public Task SaveChangesAsync(CancellationToken cancellationToken = default); } public abstract class JwksDbContextBase : DbContext, IJwksDbContext { protected JwksDbContextBase(DbContextOptions options) : base(options) { } public DbSet KeyInfos { get; set; } = null!; protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.HasPostgresExtension("pg_trgm"); foreach(var entity in builder.Model.GetEntityTypes()) { foreach(var property in entity.GetProperties()) { var memberInfo = property.PropertyInfo ?? (MemberInfo ?)property.FieldInfo; if (memberInfo == null) continue; var customRangeAttribute = memberInfo.GetCustomAttribute(); if (customRangeAttribute != null) { var actualType = Nullable.GetUnderlyingType(property.ClrType) ?? property.ClrType; if (actualType == typeof(string) || typeof(IEnumerable).IsAssignableFrom(actualType)) { property.SetMaxLength(customRangeAttribute.Maximum > int.MaxValue ? int.MaxValue : (int)customRangeAttribute.Maximum); } } var customRequiredAttribute = memberInfo.GetCustomAttribute(); if (customRequiredAttribute != null) { property.IsNullable = false; } } } builder.Entity(e => { // Для поля id дополнительные конфигурации свойства не нужны // Для поля id сопоствалений с фильтром и сортировкой не найденно e.Property(p => p.PublicKey) .HasColumnType("jsonb") .HasConversion(new CustomJsonConverter()); e.HasIndex(p => p.PublicKey) .HasMethod("gin") .HasOperators("jsonb_path_ops"); e.Property(p => p.CreatedAt) .HasColumnType("timestamp without time zone") .HasConversion(DateTimeUtcConverter.Instance); // Для поля created_at сопоствалений с фильтром и сортировкой не найденно }); builder.HasDefaultSchema("jwks"); } } /// /// Ключ. /// # Описание модели /// [ Display(Name = @"Ключ"), Description(@"Описание модели") ] public partial class KeyInfoModel { /// /// Идентификатор ключа, соответсвует kid. /// # Тип: Guid /// [ Display(Name = @"Идентификатор ключа, соответсвует kid"), Description(@"Тип: Guid"), CustomRequired, CustomPattern(@"^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$") ] public Guid Id { get; set; } /// /// Публичный ключ /// [ Display(Name = @"Публичный ключ"), CustomRequired ] public KeyInfoModel.PublicKeyModel PublicKey { get; set; } = null!; /// /// Дата создания. /// # Тип: DateTime /// [ Display(Name = @"Дата создания"), Description(@"Тип: DateTime"), Editable(false) ] public DateTime CreatedAt { get; set; } /// /// Публичный ключ /// [ Display(Name = @"Публичный ключ") ] public partial class PublicKeyModel { /// /// Вид использования. /// # Диапазон: 2..32 /// [ Display(Name = @"Вид использования"), Description(@"Диапазон: 2..32"), CustomRequired, CustomRange(2, 32) ] public string Use { get; set; } = null!; /// /// Тип ключа. /// # Диапазон: 2..32 /// [ Display(Name = @"Тип ключа"), Description(@"Диапазон: 2..32"), CustomRequired, CustomRange(2, 32) ] public string Kty { get; set; } = null!; /// /// Идентификатор. /// # Тип: Guid /// [ Display(Name = @"Идентификатор"), Description(@"Тип: Guid"), CustomRequired, CustomPattern(@"^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$") ] public Guid Kid { get; set; } /// /// Алгоритм шифрования. /// # Диапазон: 2..32 /// [ Display(Name = @"Алгоритм шифрования"), Description(@"Диапазон: 2..32"), CustomRange(2, 32) ] public string? Alg { get; set; } /// /// Параметр Modulus /// [ Display(Name = @"Параметр Modulus"), CustomRequired ] public string N { get; set; } = null!; /// /// Параметр Exponent /// [ Display(Name = @"Параметр Exponent"), CustomRequired ] public string E { get; set; } = null!; /// /// Цепочка сертификатов X.509. /// # Диапазон: 0..100 /// [ Display(Name = @"Цепочка сертификатов X.509"), Description(@"Диапазон: 0..100"), CustomRange(0, 100) ] public List X5c { get; set; } = new(0); } /// /// Ошибка сохранения. /// Эти проверки выполняются при работе с базой данных и сторонними сервисами /// [ Display(Name = @"Ошибка сохранения"), Description(@"Эти проверки выполняются при работе с базой данных и сторонними сервисами") ] public partial class SavingErrorModel { /// /// Ключ с таким идентификатором уже существует /// [ Display(Name = @"Ключ с таким идентификатором уже существует") ] public KeyInfoModel.SavingErrorModel.KeyAlreadyExistsModel? KeyAlreadyExists { get; set; } /// /// Ключ с таким идентификатором уже существует. /// Причины: /// - В базе хранится запись с переданнм kid /// [ Display(Name = @"Ключ с таким идентификатором уже существует"), Description(@"Причины: - В базе хранится запись с переданнм kid") ] public partial class KeyAlreadyExistsModel { } } } /// /// Запрос удаления публичного ключа /// [ Display(Name = @"Запрос удаления публичного ключа") ] public partial class DeleteKeyRequestModel { /// /// Идентификатор ключа, соответсвует kid. /// # Тип: Guid /// [ Display(Name = @"Идентификатор ключа, соответсвует kid"), Description(@"Тип: Guid"), CustomRequired, CustomPattern(@"^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$") ] public Guid Id { get; set; } } /// /// Ответ на запрос удаления публичного ключа /// [ Display(Name = @"Ответ на запрос удаления публичного ключа") ] public partial class DeleteKeyResponseModel { /// /// Ошибка /// [ Display(Name = @"Ошибка") ] public DeleteKeyResponseModel.ErrorModel? Error { get; set; } /// /// Ошибка запроса удаления публичного ключа /// [ Display(Name = @"Ошибка запроса удаления публичного ключа") ] public partial class ErrorModel { /// /// Ошибка валидации /// [ Display(Name = @"Ошибка валидации") ] public ValidationErrorModel? Validation { get; set; } } } /// /// Запрос на получение публичных ключей /// [ Display(Name = @"Запрос на получение публичных ключей") ] public partial class GetKeyWellKnownJwksJsonRequestModel { } /// /// Ответ на запрос на получение публичных ключей /// [ Display(Name = @"Ответ на запрос на получение публичных ключей") ] public partial class GetKeyWellKnownJwksJsonResponseModel { /// /// Список ключей /// [ Display(Name = @"Список ключей") ] public List Data { get; set; } = new(0); } /// /// Запрос на сохранение публичного ключа /// [ Display(Name = @"Запрос на сохранение публичного ключа") ] public partial class PostKeyRequestModel { /// /// Ключ /// [ Display(Name = @"Ключ"), CustomRequired ] public KeyInfoModel.PublicKeyModel Data { get; set; } = null!; } /// /// Ответ на запрос на сохранение публичного ключа /// [ Display(Name = @"Ответ на запрос на сохранение публичного ключа") ] public partial class PostKeyResponseModel { /// /// Ошибка /// [ Display(Name = @"Ошибка") ] public PostKeyResponseModel.ErrorModel? Error { get; set; } /// /// Ошибка /// [ Display(Name = @"Ошибка") ] public partial class ErrorModel { /// /// Ошибки валидации /// [ Display(Name = @"Ошибки валидации") ] public ValidationErrorModel? Validation { get; set; } /// /// Ошибка сохранения /// [ Display(Name = @"Ошибка сохранения") ] public KeyInfoModel.SavingErrorModel? Saving { get; set; } } } /// /// Ошибки валидации. /// Эти проверки выполняются до обращения в базу данных /// [ Display(Name = @"Ошибки валидации"), Description(@"Эти проверки выполняются до обращения в базу данных") ] public partial class ValidationErrorModel { /// /// Путь к полю в формате наименования прото /// [ Display(Name = @"Путь к полю в формате наименования прото"), CustomRequired ] public string Path { get; set; } = null!; /// /// Валидационное сообщение /// [ Display(Name = @"Валидационное сообщение"), CustomRequired ] public string Message { get; set; } = null!; } /// /// Запрос проверки доступности сервиса /// [ Display(Name = @"Запрос проверки доступности сервиса") ] public partial class GetSystemStatusRequestModel { } /// /// Ответ на запрос проверки доступности сервиса /// [ Display(Name = @"Ответ на запрос проверки доступности сервиса") ] public partial class GetSystemStatusResponseModel { }