diff --git a/src/BUTR.CrashReport.Server.Base/Contexts/Config/FileEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Base/Contexts/Config/FileEntityConfiguration.cs deleted file mode 100644 index 8eae98f..0000000 --- a/src/BUTR.CrashReport.Server.Base/Contexts/Config/FileEntityConfiguration.cs +++ /dev/null @@ -1,24 +0,0 @@ -using BUTR.CrashReport.Server.Models.Database; - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace BUTR.CrashReport.Server.Contexts.Config; - -public class FileEntityConfiguration : BaseEntityConfiguration -{ - protected override void ConfigureModel(EntityTypeBuilder builder) - { - builder.Property(x => x.FileId).HasColumnName("file_id"); - builder.Property(x => x.DataCompressed).HasColumnName("data_compressed"); - builder.ToTable("file_entity").HasKey(x => x.FileId).HasName("file_entity_pkey"); - - builder.HasOne(x => x.Id) - .WithOne() - .HasForeignKey(x => x.FileId) - .HasPrincipalKey(x => x.FileId) - .OnDelete(DeleteBehavior.Cascade); - - builder.Navigation(x => x.Id).AutoInclude(); - } -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Contexts/Config/JsonEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Base/Contexts/Config/JsonEntityConfiguration.cs deleted file mode 100644 index 7970729..0000000 --- a/src/BUTR.CrashReport.Server.Base/Contexts/Config/JsonEntityConfiguration.cs +++ /dev/null @@ -1,24 +0,0 @@ -using BUTR.CrashReport.Server.Models.Database; - -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace BUTR.CrashReport.Server.Contexts.Config; - -public class JsonEntityConfiguration : BaseEntityConfiguration -{ - protected override void ConfigureModel(EntityTypeBuilder builder) - { - builder.Property(x => x.FileId).HasColumnName("file_id"); - builder.Property(x => x.CrashReport).HasColumnName("data").HasColumnType("jsonb"); - builder.ToTable("json_entity").HasKey(x => x.FileId); - - builder.HasOne(x => x.Id) - .WithOne() - .HasForeignKey(x => x.FileId) - .HasPrincipalKey(x => x.FileId) - .OnDelete(DeleteBehavior.Cascade); - - builder.Navigation(x => x.Id).AutoInclude(); - } -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Models/Database/FileEntity.cs b/src/BUTR.CrashReport.Server.Base/Models/Database/FileEntity.cs deleted file mode 100644 index b4fd4ae..0000000 --- a/src/BUTR.CrashReport.Server.Base/Models/Database/FileEntity.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BUTR.CrashReport.Server.Models.Database; - -public sealed record FileEntity : IEntity -{ - public required string FileId { get; set; } - public IdEntity? Id { get; set; } - public required byte[] DataCompressed { get; set; } -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Models/Database/JsonEntity.cs b/src/BUTR.CrashReport.Server.Base/Models/Database/JsonEntity.cs deleted file mode 100644 index 5757e6b..0000000 --- a/src/BUTR.CrashReport.Server.Base/Models/Database/JsonEntity.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BUTR.CrashReport.Server.Models.Database; - -public sealed record JsonEntity : IEntity -{ - public required string FileId { get; set; } - public IdEntity? Id { get; set; } - public required string CrashReport { get; set; } -} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/BUTR.CrashReport.Server.Base.csproj b/src/BUTR.CrashReport.Server.Persistence/BUTR.CrashReport.Server.Persistence.csproj similarity index 100% rename from src/BUTR.CrashReport.Server.Base/BUTR.CrashReport.Server.Base.csproj rename to src/BUTR.CrashReport.Server.Persistence/BUTR.CrashReport.Server.Persistence.csproj diff --git a/src/BUTR.CrashReport.Server.Base/Contexts/AppDbContext.cs b/src/BUTR.CrashReport.Server.Persistence/Contexts/AppDbContext.cs similarity index 73% rename from src/BUTR.CrashReport.Server.Base/Contexts/AppDbContext.cs rename to src/BUTR.CrashReport.Server.Persistence/Contexts/AppDbContext.cs index b013594..2998feb 100644 --- a/src/BUTR.CrashReport.Server.Base/Contexts/AppDbContext.cs +++ b/src/BUTR.CrashReport.Server.Persistence/Contexts/AppDbContext.cs @@ -7,8 +7,9 @@ namespace BUTR.CrashReport.Server.Contexts; public class AppDbContext : DbContext { + public DbSet ReportEntities { get; set; } public DbSet IdEntities { get; set; } - public DbSet FileEntities { get; set; } + public DbSet HtmlEntities { get; set; } public DbSet JsonEntities { get; set; } public AppDbContext(DbContextOptions options) : base(options) { } @@ -17,8 +18,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + modelBuilder.ApplyConfiguration(new ReportEntityConfiguration()); modelBuilder.ApplyConfiguration(new IdEntityConfiguration()); - modelBuilder.ApplyConfiguration(new FileEntityConfiguration()); + modelBuilder.ApplyConfiguration(new HtmlEntityConfiguration()); modelBuilder.ApplyConfiguration(new JsonEntityConfiguration()); } } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Contexts/Config/BaseEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/BaseEntityConfiguration.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Contexts/Config/BaseEntityConfiguration.cs rename to src/BUTR.CrashReport.Server.Persistence/Contexts/Config/BaseEntityConfiguration.cs diff --git a/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/HtmlEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/HtmlEntityConfiguration.cs new file mode 100644 index 0000000..dd86718 --- /dev/null +++ b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/HtmlEntityConfiguration.cs @@ -0,0 +1,23 @@ +using BUTR.CrashReport.Server.Models.Database; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace BUTR.CrashReport.Server.Contexts.Config; + +public class HtmlEntityConfiguration : BaseEntityConfiguration +{ + protected override void ConfigureModel(EntityTypeBuilder builder) + { + builder.Property(x => x.CrashReportId).HasColumnName("crash_report_id"); + builder.Property(x => x.DataCompressed).HasColumnName("data_compressed"); + builder.ToTable("html_entity").HasKey(x => x.CrashReportId).HasName("html_entity_pkey"); + + builder.HasOne(x => x.Id) + .WithOne() + .HasForeignKey(x => x.CrashReportId) + .HasPrincipalKey(x => x.CrashReportId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("html_entity_id_entity_fkey"); + } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Contexts/Config/IdEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/IdEntityConfiguration.cs similarity index 66% rename from src/BUTR.CrashReport.Server.Base/Contexts/Config/IdEntityConfiguration.cs rename to src/BUTR.CrashReport.Server.Persistence/Contexts/Config/IdEntityConfiguration.cs index 5fbb6ed..4563cd8 100644 --- a/src/BUTR.CrashReport.Server.Base/Contexts/Config/IdEntityConfiguration.cs +++ b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/IdEntityConfiguration.cs @@ -9,12 +9,10 @@ public class IdEntityConfiguration : BaseEntityConfiguration { protected override void ConfigureModel(EntityTypeBuilder builder) { - builder.Property(x => x.FileId).HasColumnName("file_id"); builder.Property(x => x.CrashReportId).HasColumnName("crash_report_id"); - builder.Property(x => x.Version).HasColumnName("version"); - builder.Property(x => x.Created).HasColumnName("created"); - builder.ToTable("id_entity").HasKey(x => x.FileId); + builder.Property(x => x.FileId).HasColumnName("file_id"); + builder.ToTable("id_entity").HasKey(x => x.CrashReportId).HasName("id_entity_pkey"); - builder.HasIndex(x => x.CrashReportId).IsUnique(false); + builder.HasIndex(x => x.FileId).IsUnique(false).HasDatabaseName("id_entity_file_id_idx"); } } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/JsonEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/JsonEntityConfiguration.cs new file mode 100644 index 0000000..82fab41 --- /dev/null +++ b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/JsonEntityConfiguration.cs @@ -0,0 +1,23 @@ +using BUTR.CrashReport.Server.Models.Database; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace BUTR.CrashReport.Server.Contexts.Config; + +public class JsonEntityConfiguration : BaseEntityConfiguration +{ + protected override void ConfigureModel(EntityTypeBuilder builder) + { + builder.Property(x => x.CrashReportId).HasColumnName("crash_report_id"); + builder.Property(x => x.Json).HasColumnName("data").HasColumnType("jsonb"); + builder.ToTable("json_entity").HasKey(x => x.CrashReportId); + + builder.HasOne(x => x.Id) + .WithOne() + .HasForeignKey(x => x.CrashReportId) + .HasPrincipalKey(x => x.CrashReportId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("json_entity_id_entity_fkey"); + } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/ReportEntityConfiguration.cs b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/ReportEntityConfiguration.cs new file mode 100644 index 0000000..2a54ebc --- /dev/null +++ b/src/BUTR.CrashReport.Server.Persistence/Contexts/Config/ReportEntityConfiguration.cs @@ -0,0 +1,39 @@ +using BUTR.CrashReport.Server.Models.Database; + +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace BUTR.CrashReport.Server.Contexts.Config; + +public class ReportEntityConfiguration : BaseEntityConfiguration +{ + protected override void ConfigureModel(EntityTypeBuilder builder) + { + builder.Property(x => x.CrashReportId).HasColumnName("crash_report_id"); + builder.Property(x => x.Tenant).HasColumnName("tenant"); + builder.Property(x => x.Version).HasColumnName("version"); + builder.Property(x => x.Created).HasColumnName("created"); + builder.ToTable("report_entity").HasKey(x => x.CrashReportId).HasName("report_entity_pkey"); + + builder.HasOne(x => x.Id) + .WithOne(x => x.Report) + .HasForeignKey(x => x.CrashReportId) + .HasPrincipalKey(x => x.CrashReportId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("report_entity_id_entity_fkey"); + + builder.HasOne(x => x.Html) + .WithOne(x => x.Report) + .HasForeignKey(x => x.CrashReportId) + .HasPrincipalKey(x => x.CrashReportId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("report_entity_html_entity_fkey"); + + builder.HasOne(x => x.Json) + .WithOne(x => x.Report) + .HasForeignKey(x => x.CrashReportId) + .HasPrincipalKey(x => x.CrashReportId) + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("report_entity_json_entity_fkey"); + } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Extensions/HostExtensions.cs b/src/BUTR.CrashReport.Server.Persistence/Extensions/HostExtensions.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Extensions/HostExtensions.cs rename to src/BUTR.CrashReport.Server.Persistence/Extensions/HostExtensions.cs diff --git a/src/BUTR.CrashReport.Server.Base/Extensions/TextExtensions.cs b/src/BUTR.CrashReport.Server.Persistence/Extensions/TextExtensions.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Extensions/TextExtensions.cs rename to src/BUTR.CrashReport.Server.Persistence/Extensions/TextExtensions.cs diff --git a/src/BUTR.CrashReport.Server.Base/Extensions/UnicodeStream.cs b/src/BUTR.CrashReport.Server.Persistence/Extensions/UnicodeStream.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Extensions/UnicodeStream.cs rename to src/BUTR.CrashReport.Server.Persistence/Extensions/UnicodeStream.cs diff --git a/src/BUTR.CrashReport.Server.Persistence/Models/Database/HtmlEntity.cs b/src/BUTR.CrashReport.Server.Persistence/Models/Database/HtmlEntity.cs new file mode 100644 index 0000000..d7f2776 --- /dev/null +++ b/src/BUTR.CrashReport.Server.Persistence/Models/Database/HtmlEntity.cs @@ -0,0 +1,12 @@ +using System; + +namespace BUTR.CrashReport.Server.Models.Database; + +public sealed record HtmlEntity : IEntity +{ + public required Guid CrashReportId { get; set; } + public required byte[] DataCompressed { get; set; } + + public ReportEntity? Report { get; set; } + public IdEntity? Id { get; set; } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Models/Database/IEntity.cs b/src/BUTR.CrashReport.Server.Persistence/Models/Database/IEntity.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Models/Database/IEntity.cs rename to src/BUTR.CrashReport.Server.Persistence/Models/Database/IEntity.cs diff --git a/src/BUTR.CrashReport.Server.Base/Models/Database/IdEntity.cs b/src/BUTR.CrashReport.Server.Persistence/Models/Database/IdEntity.cs similarity index 68% rename from src/BUTR.CrashReport.Server.Base/Models/Database/IdEntity.cs rename to src/BUTR.CrashReport.Server.Persistence/Models/Database/IdEntity.cs index 8436238..9bf3c8e 100644 --- a/src/BUTR.CrashReport.Server.Base/Models/Database/IdEntity.cs +++ b/src/BUTR.CrashReport.Server.Persistence/Models/Database/IdEntity.cs @@ -4,8 +4,8 @@ namespace BUTR.CrashReport.Server.Models.Database; public sealed record IdEntity : IEntity { - public required string FileId { get; set; } public required Guid CrashReportId { get; set; } - public required byte Version { get; set; } - public required DateTime Created { get; set; } + public required string FileId { get; set; } + + public ReportEntity? Report { get; set; } } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Persistence/Models/Database/JsonEntity.cs b/src/BUTR.CrashReport.Server.Persistence/Models/Database/JsonEntity.cs new file mode 100644 index 0000000..815eb29 --- /dev/null +++ b/src/BUTR.CrashReport.Server.Persistence/Models/Database/JsonEntity.cs @@ -0,0 +1,12 @@ +using System; + +namespace BUTR.CrashReport.Server.Models.Database; + +public sealed record JsonEntity : IEntity +{ + public required Guid CrashReportId { get; set; } + public required string Json { get; set; } + + public ReportEntity? Report { get; set; } + public IdEntity? Id { get; set; } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Persistence/Models/Database/ReportEntity.cs b/src/BUTR.CrashReport.Server.Persistence/Models/Database/ReportEntity.cs new file mode 100644 index 0000000..a3bd171 --- /dev/null +++ b/src/BUTR.CrashReport.Server.Persistence/Models/Database/ReportEntity.cs @@ -0,0 +1,15 @@ +using System; + +namespace BUTR.CrashReport.Server.Models.Database; + +public sealed record ReportEntity : IEntity +{ + public required Guid CrashReportId { get; set; } + public required byte Tenant { get; set; } + public required byte Version { get; set; } + public required DateTime Created { get; set; } + + public IdEntity? Id { get; set; } + public HtmlEntity? Html { get; set; } + public JsonEntity? Json { get; set; } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.Base/Options/AuthOptions.cs b/src/BUTR.CrashReport.Server.Persistence/Options/AuthOptions.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Options/AuthOptions.cs rename to src/BUTR.CrashReport.Server.Persistence/Options/AuthOptions.cs diff --git a/src/BUTR.CrashReport.Server.Base/Options/CrashUploadOptions.cs b/src/BUTR.CrashReport.Server.Persistence/Options/CrashUploadOptions.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Options/CrashUploadOptions.cs rename to src/BUTR.CrashReport.Server.Persistence/Options/CrashUploadOptions.cs diff --git a/src/BUTR.CrashReport.Server.Base/Options/StorageOptions.cs b/src/BUTR.CrashReport.Server.Persistence/Options/StorageOptions.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Options/StorageOptions.cs rename to src/BUTR.CrashReport.Server.Persistence/Options/StorageOptions.cs diff --git a/src/BUTR.CrashReport.Server.Base/Services/BasicUserValidationService.cs b/src/BUTR.CrashReport.Server.Persistence/Services/BasicUserValidationService.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Services/BasicUserValidationService.cs rename to src/BUTR.CrashReport.Server.Persistence/Services/BasicUserValidationService.cs diff --git a/src/BUTR.CrashReport.Server.Base/Services/DatabaseMigrator.cs b/src/BUTR.CrashReport.Server.Persistence/Services/DatabaseMigrator.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Services/DatabaseMigrator.cs rename to src/BUTR.CrashReport.Server.Persistence/Services/DatabaseMigrator.cs diff --git a/src/BUTR.CrashReport.Server.Base/Services/FileIdGenerator.cs b/src/BUTR.CrashReport.Server.Persistence/Services/FileIdGenerator.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Services/FileIdGenerator.cs rename to src/BUTR.CrashReport.Server.Persistence/Services/FileIdGenerator.cs diff --git a/src/BUTR.CrashReport.Server.Base/Services/GZipCompressor.cs b/src/BUTR.CrashReport.Server.Persistence/Services/GZipCompressor.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Services/GZipCompressor.cs rename to src/BUTR.CrashReport.Server.Persistence/Services/GZipCompressor.cs diff --git a/src/BUTR.CrashReport.Server.Base/Services/HexGenerator.cs b/src/BUTR.CrashReport.Server.Persistence/Services/HexGenerator.cs similarity index 100% rename from src/BUTR.CrashReport.Server.Base/Services/HexGenerator.cs rename to src/BUTR.CrashReport.Server.Persistence/Services/HexGenerator.cs diff --git a/src/BUTR.CrashReport.Server.sln b/src/BUTR.CrashReport.Server.sln index ff98004..e11b8e0 100644 --- a/src/BUTR.CrashReport.Server.sln +++ b/src/BUTR.CrashReport.Server.sln @@ -25,7 +25,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BUTR.CrashReport.Server.v13 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BUTR.CrashReport.Server.v14", "BUTR.CrashReport.Server.v14\BUTR.CrashReport.Server.v14.csproj", "{0448DA45-5D06-4D7B-9F0D-37BCB26BD82F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BUTR.CrashReport.Server.Base", "BUTR.CrashReport.Server.Base\BUTR.CrashReport.Server.Base.csproj", "{1EB86E89-9CEE-465D-8F0F-69BB0D06A3DA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BUTR.CrashReport.Server.Persistence", "BUTR.CrashReport.Server.Persistence\BUTR.CrashReport.Server.Persistence.csproj", "{1EB86E89-9CEE-465D-8F0F-69BB0D06A3DA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/BUTR.CrashReport.Server.v13/BUTR.CrashReport.Server.v13.csproj b/src/BUTR.CrashReport.Server.v13/BUTR.CrashReport.Server.v13.csproj index 82dd011..de00713 100644 --- a/src/BUTR.CrashReport.Server.v13/BUTR.CrashReport.Server.v13.csproj +++ b/src/BUTR.CrashReport.Server.v13/BUTR.CrashReport.Server.v13.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/BUTR.CrashReport.Server.v13/HtmlHandlerV13.cs b/src/BUTR.CrashReport.Server.v13/HtmlHandlerV13.cs index bfb6126..5de76f1 100644 --- a/src/BUTR.CrashReport.Server.v13/HtmlHandlerV13.cs +++ b/src/BUTR.CrashReport.Server.v13/HtmlHandlerV13.cs @@ -35,6 +35,7 @@ public class HtmlHandlerV13 private readonly FileIdGenerator _fileIdGenerator; private CrashUploadOptions _options; + private readonly Counter _reportTenant; private readonly Counter _reportVersion; public HtmlHandlerV13( @@ -46,6 +47,7 @@ public HtmlHandlerV13( IMeterFactory meterFactory) { var meter = meterFactory.Create("BUTR.CrashReportServer.Controllers.CrashUploadController", "1.0.0"); + _reportTenant = meter.CreateCounter("report-tenant", unit: "Count"); _reportVersion = meter.CreateCounter("report-version", unit: "Count"); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -61,16 +63,18 @@ public async Task UploadHtmlAsync(ControllerBase controller, Canc { controller.Request.EnableBuffering(); + var tenant = byte.TryParse(controller.Request.Headers["Tenant"].ToString(), out var tenantId) ? tenantId : (byte) 0; + using var streamReader = new StreamReader(controller.Request.Body); var html = await streamReader.ReadToEndAsync(ct); var (valid, version, crashReportModel) = ParseHtml(html); - if (!valid) + if (!valid || crashReportModel is null) { _logger.LogWarning("Invalid HTML"); return controller.StatusCode(StatusCodes.Status500InternalServerError); } - if (await _dbContext.IdEntities.FirstOrDefaultAsync(x => x.CrashReportId == crashReportModel!.Id, ct) is { } idEntity) + if (await _dbContext.IdEntities.FirstOrDefaultAsync(x => x.CrashReportId == crashReportModel.Id, ct) is { } idEntity) return controller.Ok($"{_options.BaseUri}/{idEntity.FileId}"); var json = JsonSerializer.Serialize(crashReportModel, _jsonSerializerOptionsWeb); @@ -78,15 +82,22 @@ public async Task UploadHtmlAsync(ControllerBase controller, Canc controller.Request.Body.Seek(0, SeekOrigin.Begin); await using var compressedHtmlStream = await _gZipCompressor.CompressAsync(controller.Request.Body, ct); - idEntity = new IdEntity { FileId = _fileIdGenerator.Generate(ct), CrashReportId = crashReportModel!.Id, Version = version, Created = DateTime.UtcNow, }; - await _dbContext.IdEntities.AddAsync(idEntity, ct); - await _dbContext.FileEntities.AddAsync(new FileEntity { FileId = idEntity.FileId, DataCompressed = compressedHtmlStream.ToArray(), }, ct); - if (version >= 13) await _dbContext.JsonEntities.AddAsync(new JsonEntity { FileId = idEntity.FileId, CrashReport = json, }, ct); + await _dbContext.ReportEntities.AddAsync(new ReportEntity + { + CrashReportId = crashReportModel.Id, + Tenant = tenant, + Version = version, + Created = DateTime.UtcNow, + }, ct); + await _dbContext.IdEntities.AddAsync(idEntity = new IdEntity { CrashReportId = crashReportModel.Id, FileId = _fileIdGenerator.Generate(ct), }, ct); + await _dbContext.HtmlEntities.AddAsync(new HtmlEntity { CrashReportId = crashReportModel.Id, DataCompressed = compressedHtmlStream.ToArray(), }, ct); + if (version >= 13) await _dbContext.JsonEntities.AddAsync(new JsonEntity { CrashReportId = crashReportModel.Id, Json = json, }, ct); await _dbContext.SaveChangesAsync(ct); + _reportTenant.Add(1, new[] { new KeyValuePair("Tenant", tenant) }); _reportVersion.Add(1, new[] { new KeyValuePair("Version", version) }); - return controller.Ok($"{_options.BaseUri}/{idEntity.FileId}"); + return controller.Ok(tenant == 0 ? $"{_options.BaseUri}/{idEntity.FileId}" : $"{_options.BaseUri}/{tenant}/{idEntity.FileId}"); } private static (bool isValid, byte version, CrashReportModel? crashReportModel) ParseHtml(string html) diff --git a/src/BUTR.CrashReport.Server.v13/JsonHandlerV13.cs b/src/BUTR.CrashReport.Server.v13/JsonHandlerV13.cs index f186567..f42cdaf 100644 --- a/src/BUTR.CrashReport.Server.v13/JsonHandlerV13.cs +++ b/src/BUTR.CrashReport.Server.v13/JsonHandlerV13.cs @@ -37,6 +37,7 @@ public class JsonHandlerV13 private JsonSerializerOptions _jsonSerializerOptions; private CrashUploadOptions _options; + private readonly Counter _reportTenant; private readonly Counter _reportVersion; public JsonHandlerV13( @@ -49,6 +50,7 @@ public JsonHandlerV13( IMeterFactory meterFactory) { var meter = meterFactory.Create("BUTR.CrashReportServer.Controllers.CrashUploadController", "1.0.0"); + _reportTenant = meter.CreateCounter("report-tenant", unit: "Count"); _reportVersion = meter.CreateCounter("report-version", unit: "Count"); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -64,9 +66,13 @@ public JsonHandlerV13( public async Task UploadJsonAsync(ControllerBase controller, CancellationToken ct) { + var tenant = byte.TryParse(controller.Request.Headers["Tenant"].ToString(), out var tenantId) ? tenantId : (byte) 0; + if (controller.Request.Headers.ContentEncoding.Any(x => x?.Equals("gzip,deflate", StringComparison.OrdinalIgnoreCase) == true)) controller.Request.Body = await _gZipCompressor.DecompressAsync(controller.Request.Body, ct); - + else + controller.Request.EnableBuffering(); + if (await controller.HttpContext.Request.ReadFromJsonAsync(_jsonSerializerOptions, ct) is not { CrashReport: { } crashReport, LogSources: { } logSources }) { _logger.LogWarning("Failed to read JSON body"); @@ -82,14 +88,21 @@ public async Task UploadJsonAsync(ControllerBase controller, Canc controller.Request.Body.Seek(0, SeekOrigin.Begin); await using var compressedHtmlStream = await _gZipCompressor.CompressAsync(html.AsStream(), ct); - idEntity = new IdEntity { FileId = _fileIdGenerator.Generate(ct), CrashReportId = crashReport.Id, Version = crashReport.Version, Created = DateTime.UtcNow, }; - await _dbContext.IdEntities.AddAsync(idEntity, ct); - await _dbContext.JsonEntities.AddAsync(new JsonEntity { FileId = idEntity.FileId, CrashReport = json, }, ct); - await _dbContext.FileEntities.AddAsync(new FileEntity { FileId = idEntity.FileId, DataCompressed = compressedHtmlStream.ToArray(), }, ct); + await _dbContext.ReportEntities.AddAsync(new ReportEntity + { + CrashReportId = crashReport.Id, + Tenant = tenant, + Version = crashReport.Version, + Created = DateTime.UtcNow, + }, ct); + await _dbContext.IdEntities.AddAsync(idEntity = new IdEntity { CrashReportId = crashReport.Id, FileId = _fileIdGenerator.Generate(ct), }, ct); + await _dbContext.HtmlEntities.AddAsync(new HtmlEntity { CrashReportId = crashReport.Id, DataCompressed = compressedHtmlStream.ToArray(), }, ct); + await _dbContext.JsonEntities.AddAsync(new JsonEntity { CrashReportId = crashReport.Id, Json = json, }, ct); await _dbContext.SaveChangesAsync(ct); + _reportTenant.Add(1, new[] { new KeyValuePair("Tenant", tenant) }); _reportVersion.Add(1, new[] { new KeyValuePair("Version", crashReport.Version) }); - return controller.Ok($"{_options.BaseUri}/{idEntity.FileId}"); + return controller.Ok(tenant == 0 ? $"{_options.BaseUri}/{idEntity.FileId}" : $"{_options.BaseUri}/{tenant}/{idEntity.FileId}"); } } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server.v14/BUTR.CrashReport.Server.v14.csproj b/src/BUTR.CrashReport.Server.v14/BUTR.CrashReport.Server.v14.csproj index dabf872..9fff37a 100644 --- a/src/BUTR.CrashReport.Server.v14/BUTR.CrashReport.Server.v14.csproj +++ b/src/BUTR.CrashReport.Server.v14/BUTR.CrashReport.Server.v14.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/BUTR.CrashReport.Server.v14/HtmlHandlerV14.cs b/src/BUTR.CrashReport.Server.v14/HtmlHandlerV14.cs index 9bb3987..0f28f8f 100644 --- a/src/BUTR.CrashReport.Server.v14/HtmlHandlerV14.cs +++ b/src/BUTR.CrashReport.Server.v14/HtmlHandlerV14.cs @@ -35,6 +35,7 @@ public class HtmlHandlerV14 private readonly FileIdGenerator _fileIdGenerator; private CrashUploadOptions _options; + private readonly Counter _reportTenant; private readonly Counter _reportVersion; public HtmlHandlerV14( @@ -46,6 +47,7 @@ public HtmlHandlerV14( IMeterFactory meterFactory) { var meter = meterFactory.Create("BUTR.CrashReportServer.Controllers.CrashUploadController", "1.0.0"); + _reportTenant = meter.CreateCounter("report-tenant", unit: "Count"); _reportVersion = meter.CreateCounter("report-version", unit: "Count"); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -61,16 +63,18 @@ public async Task UploadHtmlAsync(ControllerBase controller, Canc { controller.Request.EnableBuffering(); + var tenant = byte.TryParse(controller.Request.Headers["Tenant"].ToString(), out var tenantId) ? tenantId : (byte) 0; + using var streamReader = new StreamReader(controller.Request.Body); var html = await streamReader.ReadToEndAsync(ct); var (valid, version, crashReportModel) = ParseHtml(html); - if (!valid) + if (!valid || crashReportModel is null) { _logger.LogWarning("Invalid HTML"); return controller.StatusCode(StatusCodes.Status500InternalServerError); } - if (await _dbContext.IdEntities.FirstOrDefaultAsync(x => x.CrashReportId == crashReportModel!.Id, ct) is { } idEntity) + if (await _dbContext.IdEntities.FirstOrDefaultAsync(x => x.CrashReportId == crashReportModel.Id, ct) is { } idEntity) return controller.Ok($"{_options.BaseUri}/{idEntity.FileId}"); var json = JsonSerializer.Serialize(crashReportModel, _jsonSerializerOptionsWeb); @@ -78,15 +82,22 @@ public async Task UploadHtmlAsync(ControllerBase controller, Canc controller.Request.Body.Seek(0, SeekOrigin.Begin); await using var compressedHtmlStream = await _gZipCompressor.CompressAsync(controller.Request.Body, ct); - idEntity = new IdEntity { FileId = _fileIdGenerator.Generate(ct), CrashReportId = crashReportModel!.Id, Version = version, Created = DateTime.UtcNow, }; - await _dbContext.IdEntities.AddAsync(idEntity, ct); - await _dbContext.FileEntities.AddAsync(new FileEntity { FileId = idEntity.FileId, DataCompressed = compressedHtmlStream.ToArray(), }, ct); - if (version >= 13) await _dbContext.JsonEntities.AddAsync(new JsonEntity { FileId = idEntity.FileId, CrashReport = json, }, ct); + await _dbContext.ReportEntities.AddAsync(new ReportEntity + { + CrashReportId = crashReportModel.Id, + Tenant = tenant, + Version = version, + Created = DateTime.UtcNow, + }, ct); + await _dbContext.IdEntities.AddAsync(idEntity = new IdEntity { CrashReportId = crashReportModel.Id, FileId = _fileIdGenerator.Generate(ct), }, ct); + await _dbContext.HtmlEntities.AddAsync(new HtmlEntity { CrashReportId = crashReportModel.Id, DataCompressed = compressedHtmlStream.ToArray(), }, ct); + if (version >= 13) await _dbContext.JsonEntities.AddAsync(new JsonEntity { CrashReportId = crashReportModel.Id, Json = json, }, ct); await _dbContext.SaveChangesAsync(ct); + _reportTenant.Add(1, new[] { new KeyValuePair("Tenant", tenant) }); _reportVersion.Add(1, new[] { new KeyValuePair("Version", version) }); - return controller.Ok($"{_options.BaseUri}/{idEntity.FileId}"); + return controller.Ok(tenant == 0 ? $"{_options.BaseUri}/{idEntity.FileId}" : $"{_options.BaseUri}/{tenant}/{idEntity.FileId}"); } private static (bool isValid, byte version, CrashReportModel? crashReportModel) ParseHtml(string html) diff --git a/src/BUTR.CrashReport.Server.v14/JsonHandlerV14.cs b/src/BUTR.CrashReport.Server.v14/JsonHandlerV14.cs index 2c65348..50483ea 100644 --- a/src/BUTR.CrashReport.Server.v14/JsonHandlerV14.cs +++ b/src/BUTR.CrashReport.Server.v14/JsonHandlerV14.cs @@ -37,6 +37,7 @@ public class JsonHandlerV14 private CrashUploadOptions _options; private JsonSerializerOptions _jsonSerializerOptions; + private readonly Counter _reportTenant; private readonly Counter _reportVersion; public JsonHandlerV14( @@ -49,6 +50,7 @@ public JsonHandlerV14( IMeterFactory meterFactory) { var meter = meterFactory.Create("BUTR.CrashReportServer.Controllers.CrashUploadController", "1.0.0"); + _reportTenant = meter.CreateCounter("report-tenant", unit: "Count"); _reportVersion = meter.CreateCounter("report-version", unit: "Count"); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -64,8 +66,12 @@ public JsonHandlerV14( public async Task UploadJsonAsync(ControllerBase controller, CancellationToken ct) { + var tenant = byte.TryParse(controller.Request.Headers["Tenant"].ToString(), out var tenantId) ? tenantId : (byte) 0; + if (controller.Request.Headers.ContentEncoding.Any(x => x?.Equals("gzip,deflate", StringComparison.OrdinalIgnoreCase) == true)) controller.Request.Body = await _gZipCompressor.DecompressAsync(controller.Request.Body, ct); + else + controller.Request.EnableBuffering(); if (await controller.HttpContext.Request.ReadFromJsonAsync(_jsonSerializerOptions, ct) is not { CrashReport: { } crashReport, LogSources: { } logSources }) { @@ -82,14 +88,21 @@ public async Task UploadJsonAsync(ControllerBase controller, Canc controller.Request.Body.Seek(0, SeekOrigin.Begin); await using var compressedHtmlStream = await _gZipCompressor.CompressAsync(html.AsStream(), ct); - idEntity = new IdEntity { FileId = _fileIdGenerator.Generate(ct), CrashReportId = crashReport.Id, Version = crashReport.Version, Created = DateTime.UtcNow, }; - await _dbContext.IdEntities.AddAsync(idEntity, ct); - await _dbContext.JsonEntities.AddAsync(new JsonEntity { FileId = idEntity.FileId, CrashReport = json, }, ct); - await _dbContext.FileEntities.AddAsync(new FileEntity { FileId = idEntity.FileId, DataCompressed = compressedHtmlStream.ToArray(), }, ct); + await _dbContext.ReportEntities.AddAsync(new ReportEntity + { + CrashReportId = crashReport.Id, + Tenant = tenant, + Version = crashReport.Version, + Created = DateTime.UtcNow, + }, ct); + await _dbContext.IdEntities.AddAsync(idEntity = new IdEntity { CrashReportId = crashReport.Id, FileId = _fileIdGenerator.Generate(ct), }, ct); + await _dbContext.HtmlEntities.AddAsync(new HtmlEntity { CrashReportId = crashReport.Id, DataCompressed = compressedHtmlStream.ToArray(), }, ct); + await _dbContext.JsonEntities.AddAsync(new JsonEntity { CrashReportId = crashReport.Id, Json = json, }, ct); await _dbContext.SaveChangesAsync(ct); + _reportTenant.Add(1, new[] { new KeyValuePair("Tenant", tenant) }); _reportVersion.Add(1, new[] { new KeyValuePair("Version", crashReport.Version) }); - return controller.Ok($"{_options.BaseUri}/{idEntity.FileId}"); + return controller.Ok(tenant == 0 ? $"{_options.BaseUri}/{idEntity.FileId}" : $"{_options.BaseUri}/{tenant}/{idEntity.FileId}"); } } \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server/BUTR.CrashReport.Server.csproj b/src/BUTR.CrashReport.Server/BUTR.CrashReport.Server.csproj index 84c5e00..1cf9f96 100644 --- a/src/BUTR.CrashReport.Server/BUTR.CrashReport.Server.csproj +++ b/src/BUTR.CrashReport.Server/BUTR.CrashReport.Server.csproj @@ -31,12 +31,12 @@ - + - + @@ -50,7 +50,7 @@ - + diff --git a/src/BUTR.CrashReport.Server/Controllers/ReportController.cs b/src/BUTR.CrashReport.Server/Controllers/ReportController.cs index a8a8258..559546c 100644 --- a/src/BUTR.CrashReport.Server/Controllers/ReportController.cs +++ b/src/BUTR.CrashReport.Server/Controllers/ReportController.cs @@ -66,12 +66,12 @@ public ReportController(ILogger logger, IOptionsSnapshot GetHtml(string filename, CancellationToken ct) + private async Task GetHtml(byte tenant, string filename, CancellationToken ct) { if (ValidateRequest(filename) is { } errorResponse) return errorResponse; - if (await _dbContext.FileEntities.FirstOrDefaultAsync(x => x.Id!.FileId == filename, ct) is not { } file) + if (await _dbContext.HtmlEntities.FirstOrDefaultAsync(x => x.Report!.Tenant == tenant && x.Id!.FileId == filename, ct) is not { } file) return StatusCode(StatusCodes.Status404NotFound); if (Request.GetTypedHeaders().AcceptEncoding.Any(x => x.Value.Equals("gzip", StringComparison.InvariantCultureIgnoreCase))) @@ -82,15 +82,15 @@ private async Task GetHtml(string filename, CancellationToken ct) return File(await _gZipCompressor.DecompressAsync(file.DataCompressed, ct), "text/html; charset=utf-8", false); } - private async Task GetJson(string filename, CancellationToken ct) + private async Task GetJson(byte tenant, string filename, CancellationToken ct) { if (ValidateRequest(filename) is { } errorResponse) return errorResponse; - if (await _dbContext.JsonEntities.FirstOrDefaultAsync(x => x.Id!.FileId == filename, ct) is not { } file) + if (await _dbContext.JsonEntities.FirstOrDefaultAsync(x => x.Report!.Tenant == tenant && x.Id!.FileId == filename, ct) is not { } file) return StatusCode(StatusCodes.Status404NotFound); - return Content(file.CrashReport, "application/json; charset=utf-8"); + return Content(file.Json, "application/json; charset=utf-8"); } [AllowAnonymous] @@ -99,7 +99,15 @@ private async Task GetJson(string filename, CancellationToken ct) [ProducesResponseType(typeof(void), StatusCodes.Status400BadRequest, "application/problem+json")] [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound, "application/problem+json")] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] - public Task ReportHtml(string filename, CancellationToken ct) => GetHtml(filename, ct); + public Task ReportHtml(string filename, CancellationToken ct) => GetHtml(0, filename, ct); + + [AllowAnonymous] + [HttpGet("{tenant:int}/{filename}.html")] + [ProducesResponseType(typeof(void), StatusCodes.Status200OK, "text/html")] + [ProducesResponseType(typeof(void), StatusCodes.Status400BadRequest, "application/problem+json")] + [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound, "application/problem+json")] + [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] + public Task ReportHtml(byte tenant, string filename, CancellationToken ct) => GetHtml(tenant, filename, ct); [AllowAnonymous] [HttpGet("{filename}.json")] @@ -107,7 +115,15 @@ private async Task GetJson(string filename, CancellationToken ct) [ProducesResponseType(typeof(void), StatusCodes.Status400BadRequest, "application/problem+json")] [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound, "application/problem+json")] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] - public Task ReportJson(string filename, CancellationToken ct) => GetJson(filename, ct); + public Task ReportJson(string filename, CancellationToken ct) => GetJson(0, filename, ct); + + [AllowAnonymous] + [HttpGet("{tenant:int}/{filename}.json")] + [ProducesResponseType(typeof(void), StatusCodes.Status200OK, "application/json")] + [ProducesResponseType(typeof(void), StatusCodes.Status400BadRequest, "application/problem+json")] + [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound, "application/problem+json")] + [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] + public Task ReportJson(byte tenant, string filename, CancellationToken ct) => GetJson(tenant, filename, ct); [AllowAnonymous] [HttpGet("{filename}")] @@ -117,71 +133,110 @@ private async Task GetJson(string filename, CancellationToken ct) [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] public Task ReportBasedOnAccept(string filename, CancellationToken ct) => Request.Headers.Accept.FirstOrDefault(x => x is "text/html" or "application/json") switch { - "text/html" => GetHtml(filename, ct), - "application/json" => GetJson(filename, ct), - _ => GetHtml(filename, ct), + "text/html" => GetHtml(0, filename, ct), + "application/json" => GetJson(0, filename, ct), + _ => GetHtml(0, filename, ct), + }; + + [AllowAnonymous] + [HttpGet("{tenant:int}/{filename}")] + [ProducesResponseType(typeof(void), StatusCodes.Status200OK, "application/json")] + [ProducesResponseType(typeof(void), StatusCodes.Status400BadRequest, "application/problem+json")] + [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound, "application/problem+json")] + [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] + public Task ReportBasedOnAccept(byte tenant, string filename, CancellationToken ct) => Request.Headers.Accept.FirstOrDefault(x => x is "text/html" or "application/json") switch + { + "text/html" => GetHtml(tenant, filename, ct), + "application/json" => GetJson(tenant, filename, ct), + _ => GetHtml(tenant, filename, ct), }; [Authorize] - [HttpDelete("Delete/{filename}")] + [HttpDelete("Delete/{tenant:int}/{filename}")] [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] [ProducesResponseType(typeof(TLSError), StatusCodes.Status400BadRequest, "application/json")] [HttpsProtocol(Protocol = SslProtocols.Tls13)] - public async Task Delete(string filename) + public async Task Delete(byte tenant, string filename) { - if (await _dbContext.FileEntities.Where(x => x.Id!.FileId == filename).ExecuteDeleteAsync(CancellationToken.None) == 0) - return StatusCode(StatusCodes.Status404NotFound); - - await _dbContext.JsonEntities.Where(x => x.Id!.FileId == filename).ExecuteDeleteAsync(CancellationToken.None); + await _dbContext.HtmlEntities.Where(x => x.Report!.Tenant == tenant && x.Id!.FileId == filename).ExecuteDeleteAsync(CancellationToken.None); + await _dbContext.JsonEntities.Where(x => x.Report!.Tenant == tenant && x.Id!.FileId == filename).ExecuteDeleteAsync(CancellationToken.None); + await _dbContext.IdEntities.Where(x => x.FileId == filename).ExecuteDeleteAsync(CancellationToken.None); + await _dbContext.ReportEntities.Where(x => x.Id!.FileId == filename).ExecuteDeleteAsync(CancellationToken.None); return Ok(); } [Authorize] - [HttpGet("GetAllFilenames")] + [HttpGet("{tenant:int}/GetAllFilenames")] [ProducesResponseType(typeof(string[]), StatusCodes.Status200OK, "application/json")] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] [ProducesResponseType(typeof(TLSError), StatusCodes.Status400BadRequest, "application/json")] [HttpsProtocol(Protocol = SslProtocols.Tls13)] - public ActionResult> GetAllFilenames() => Ok(_dbContext.IdEntities.Select(x => x.FileId)); + public ActionResult> GetAllFilenames(byte tenant) => Ok(_dbContext.ReportEntities.Where(x => x.Tenant == tenant).Select(x => x.Id!.FileId)); [Authorize] - [HttpPost("GetMetadata")] + [HttpPost("{tenant:int}/GetMetadata")] [ProducesResponseType(typeof(FileMetadata[]), StatusCodes.Status200OK, "application/json")] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] [ProducesResponseType(typeof(TLSError), StatusCodes.Status400BadRequest, "application/json")] [HttpsProtocol(Protocol = SslProtocols.Tls13)] - public ActionResult> GetFilenameDates(ICollection filenames, CancellationToken ct) + public ActionResult> GetFilenameDates(byte tenant, ICollection filenames, CancellationToken ct) { var filenamesWithExtension = filenames.Select(Path.GetFileNameWithoutExtension).ToImmutableArray(); - return Ok(_dbContext.IdEntities - .Where(x => filenamesWithExtension.Contains(x.FileId)) - .AsEnumerable() + return Ok(_dbContext.ReportEntities + .Where(x => x.Tenant == tenant) + .Where(x => filenamesWithExtension.Contains(x.Id!.FileId)) .Select(x => new FileMetadata { - File = x.FileId, + File = x.Id!.FileId, + Id = x.CrashReportId, + Version = x.Version, + Date = x.Created, + })); + } + + [Authorize] + [HttpPost("{tenant:int}/GetNewCrashReports")] + [ProducesResponseType(typeof(FileMetadata[]), StatusCodes.Status200OK, "application/json")] + [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] + [ProducesResponseType(typeof(TLSError), StatusCodes.Status400BadRequest, "application/json")] + [HttpsProtocol(Protocol = SslProtocols.Tls13)] + public ActionResult> GetNewCrashReportsDates(byte tenant, [FromBody] GetNewCrashReportsBody body, CancellationToken ct) + { + var diff = DateTime.UtcNow - body.DateTime; + if (diff.Ticks < 0 || diff > TimeSpan.FromDays(30)) + return BadRequest(); + + return Ok(_dbContext.ReportEntities + .Where(x => x.Tenant == tenant) + .Where(x => x.Created > body.DateTime) + .Select(x => new FileMetadata + { + File = x.Id!.FileId, Id = x.CrashReportId, Version = x.Version, Date = x.Created, })); } + /* [Authorize] - [HttpPost("GetNewCrashReports")] + [HttpPost("{tenant:int}/GetNewCrashReports")] [ProducesResponseType(typeof(FileMetadata[]), StatusCodes.Status200OK, "application/json")] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+json")] [ProducesResponseType(typeof(TLSError), StatusCodes.Status400BadRequest, "application/json")] [HttpsProtocol(Protocol = SslProtocols.Tls13)] - public ActionResult> GetNewCrashReportsDates([FromBody] GetNewCrashReportsBody body, CancellationToken ct) + public ActionResult> RegenerateHtmlCrashReports(byte tenant, [FromBody] GetNewCrashReportsBody body, CancellationToken ct) { var diff = DateTime.UtcNow - body.DateTime; if (diff.Ticks < 0 || diff > TimeSpan.FromDays(30)) return BadRequest(); return Ok(_dbContext.IdEntities + .Where(x => x.Tenant == tenant) .Where(x => x.Created > body.DateTime) .Select(x => new FileMetadata { @@ -191,6 +246,7 @@ public ActionResult> GetNewCrashReportsDates([FromBody Date = x.Created, })); } + */ [AllowAnonymous] [HttpGet("sitemap_index.xml")] @@ -200,32 +256,59 @@ public ActionResult> GetNewCrashReportsDates([FromBody [ResponseCache(Duration = 60 * 60 * 4)] public IActionResult SitemapIndex() { - var count = _dbContext.IdEntities.Count(); - var sitemaps = (count / 50000) + 1; + var sitemaps = new List(); + foreach (var tenant in _dbContext.ReportEntities.Select(x => x.Tenant).Distinct()) + { + var count = _dbContext.ReportEntities.Count(x => x.Tenant == tenant); + var sitemapsCount = (count / 50000) + 1; - var sitemap = new SitemapIndex + sitemaps.AddRange(Enumerable.Range(0, sitemapsCount).Select(x => new Sitemap + { + Location = tenant == 0 + ? $"{_options.BaseUri}/sitemap_{x}.xml" + : $"{_options.BaseUri}/sitemap_{tenant}_{x}.xml", + })); + } + return Ok(new SitemapIndex { - Sitemap = Enumerable.Range(0, sitemaps).Select(x => new Sitemap + Sitemap = sitemaps, + }); + } + + [AllowAnonymous] + [HttpGet("sitemap_{idx:int}.xml")] + [Produces("application/xml")] + [ProducesResponseType(typeof(Urlset), StatusCodes.Status200OK, "application/xml")] + [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+xml")] + [ResponseCache(Duration = 60 * 60 * 4)] + public IActionResult Sitemap(int idx) + { + var sitemap = new Urlset + { + Url = _dbContext.ReportEntities.Where(x => x.Tenant == 0).OrderBy(x => x.Created).Skip(idx * 50000).Take(50000).Select(x => new { x.Id!.FileId, x.Created }).Select(x => new Url { - Location = $"{_options.BaseUri}/sitemap_{x}.xml", + Location = $"{_options.BaseUri}/{x.FileId}", + TimeStamp = x.Created, + Priority = 0.5, + ChangeFrequency = ChangeFrequency.Never, }).ToList(), }; return Ok(sitemap); } [AllowAnonymous] - [HttpGet("sitemap_{idx:int}.xml")] + [HttpGet("sitemap_{tenant:int}_{idx:int}.xml")] [Produces("application/xml")] [ProducesResponseType(typeof(Urlset), StatusCodes.Status200OK, "application/xml")] [ProducesResponseType(typeof(void), StatusCodes.Status500InternalServerError, "application/problem+xml")] [ResponseCache(Duration = 60 * 60 * 4)] - public IActionResult Sitemap(int idx) + public IActionResult Sitemap(byte tenant, int idx) { var sitemap = new Urlset { - Url = _dbContext.IdEntities.OrderBy(x => x.Created).Skip(idx * 50000).Take(50000).Select(x => new { x.FileId, x.Created }).Select(x => new Url + Url = _dbContext.ReportEntities.Where(x => x.Tenant == tenant).OrderBy(x => x.Created).Skip(idx * 50000).Take(50000).Select(x => new { x.Id!.FileId, x.Created }).Select(x => new Url { - Location = $"{_options.BaseUri}/{x.FileId}", + Location = $"{_options.BaseUri}/{tenant}/{x.FileId}", TimeStamp = x.Created, Priority = 0.5, ChangeFrequency = ChangeFrequency.Never, diff --git a/src/BUTR.CrashReport.Server/Dockerfile b/src/BUTR.CrashReport.Server/Dockerfile index 7b3b235..75e8484 100644 --- a/src/BUTR.CrashReport.Server/Dockerfile +++ b/src/BUTR.CrashReport.Server/Dockerfile @@ -2,7 +2,7 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS restore ARG TARGETARCH WORKDIR /build -COPY ["src/BUTR.CrashReport.Server.Base/BUTR.CrashReport.Server.Base.csproj", "src/BUTR.CrashReport.Server.Base/"] +COPY ["src/BUTR.CrashReport.Server.Persistence/BUTR.CrashReport.Server.Persistence.csproj", "src/BUTR.CrashReport.Server.Persistence/"] COPY ["src/BUTR.CrashReport.Server.v13/BUTR.CrashReport.Server.v13.csproj", "src/BUTR.CrashReport.Server.v13/"] COPY ["src/BUTR.CrashReport.Server.v14/BUTR.CrashReport.Server.v14.csproj", "src/BUTR.CrashReport.Server.v14/"] COPY ["src/BUTR.CrashReport.Server/BUTR.CrashReport.Server.csproj", "src/BUTR.CrashReport.Server/"] @@ -10,7 +10,7 @@ COPY ["src/nuget.config", "src/"] RUN dotnet restore "src/BUTR.CrashReport.Server/BUTR.CrashReport.Server.csproj" -a $TARGETARCH; -COPY ["src/BUTR.CrashReport.Server.Base/", "src/BUTR.CrashReport.Server.Base/"] +COPY ["src/BUTR.CrashReport.Server.Persistence/", "src/BUTR.CrashReport.Server.Persistence/"] COPY ["src/BUTR.CrashReport.Server.v13/", "src/BUTR.CrashReport.Server.v13/"] COPY ["src/BUTR.CrashReport.Server.v14/", "src/BUTR.CrashReport.Server.v14/"] COPY ["src/BUTR.CrashReport.Server/", "src/BUTR.CrashReport.Server/"] diff --git a/src/BUTR.CrashReport.Server/Migrations/20241009200831_NewMetadata.Designer.cs b/src/BUTR.CrashReport.Server/Migrations/20241009200831_NewMetadata.Designer.cs new file mode 100644 index 0000000..ba3cd03 --- /dev/null +++ b/src/BUTR.CrashReport.Server/Migrations/20241009200831_NewMetadata.Designer.cs @@ -0,0 +1,169 @@ +// +using System; +using BUTR.CrashReport.Server.Contexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace BUTR.CrashReport.Server.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241009200831_NewMetadata")] + partial class NewMetadata + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.HtmlEntity", b => + { + b.Property("CrashReportId") + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); + + b.Property("DataCompressed") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("data_compressed"); + + b.HasKey("CrashReportId") + .HasName("file_entity_pkey"); + + b.ToTable("file_entity", (string)null); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.IdEntity", b => + { + b.Property("CrashReportId") + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); + + b.Property("FileId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("file_id"); + + b.HasKey("CrashReportId"); + + b.HasIndex("FileId"); + + b.ToTable("id_test_entity", (string)null); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => + { + b.Property("CrashReportId") + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("data"); + + b.HasKey("CrashReportId"); + + b.ToTable("json_entity", (string)null); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.ReportEntity", b => + { + b.Property("CrashReportId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Tenant") + .HasColumnType("smallint") + .HasColumnName("tenant"); + + b.Property("Version") + .HasColumnType("smallint") + .HasColumnName("version"); + + b.HasKey("CrashReportId"); + + b.ToTable("id_entity", (string)null); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.HtmlEntity", b => + { + b.HasOne("BUTR.CrashReport.Server.Models.Database.ReportEntity", "Report") + .WithOne("Html") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.HtmlEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.IdEntity", b => + { + b.HasOne("BUTR.CrashReport.Server.Models.Database.HtmlEntity", null) + .WithOne("Id") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.IdEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BUTR.CrashReport.Server.Models.Database.JsonEntity", null) + .WithOne("Id") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.IdEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BUTR.CrashReport.Server.Models.Database.ReportEntity", "Report") + .WithOne("Id") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.IdEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => + { + b.HasOne("BUTR.CrashReport.Server.Models.Database.ReportEntity", "Report") + .WithOne("Json") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.JsonEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Report"); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.HtmlEntity", b => + { + b.Navigation("Id"); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => + { + b.Navigation("Id"); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.ReportEntity", b => + { + b.Navigation("Html"); + + b.Navigation("Id"); + + b.Navigation("Json"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/BUTR.CrashReport.Server/Migrations/20241009200831_NewMetadata.cs b/src/BUTR.CrashReport.Server/Migrations/20241009200831_NewMetadata.cs new file mode 100644 index 0000000..8e947f3 --- /dev/null +++ b/src/BUTR.CrashReport.Server/Migrations/20241009200831_NewMetadata.cs @@ -0,0 +1,181 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +using System; + +#nullable disable + +namespace BUTR.CrashReport.Server.Migrations +{ + public partial class NewMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "crash_report_id", + table: "file_entity", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + migrationBuilder.Sql(""" + UPDATE file_entity fe + SET crash_report_id = (SELECT ie.crash_report_id FROM id_entity ie WHERE ie.file_id = fe.file_id) + """); + migrationBuilder.DropForeignKey( + name: "FK_file_entity_id_entity_file_id", + table: "file_entity"); + migrationBuilder.Sql(""" + DELETE FROM file_entity T1 + USING file_entity T2 + WHERE T1.ctid < T2.ctid + AND T1.crash_report_id = T2.crash_report_id; + """); + + + migrationBuilder.AddColumn( + name: "crash_report_id", + table: "json_entity", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + migrationBuilder.Sql(""" + UPDATE json_entity je + SET crash_report_id = (SELECT ie.crash_report_id FROM id_entity ie WHERE ie.file_id = je.file_id) + """); + migrationBuilder.DropForeignKey( + name: "FK_json_entity_id_entity_file_id", + table: "json_entity"); + migrationBuilder.Sql(""" + DELETE FROM json_entity T1 + USING json_entity T2 + WHERE T1.ctid < T2.ctid + AND T1.crash_report_id = T2.crash_report_id; + """); + + + migrationBuilder.DropPrimaryKey( + name: "file_entity_pkey", + table: "file_entity"); + migrationBuilder.DropColumn( + name: "file_id", + table: "file_entity"); + migrationBuilder.AddPrimaryKey( + name: "html_entity_pkey", + table: "file_entity", + column: "crash_report_id"); + + + + migrationBuilder.DropPrimaryKey( + name: "PK_json_entity", + table: "json_entity"); + migrationBuilder.DropColumn( + name: "file_id", + table: "json_entity"); + migrationBuilder.AddPrimaryKey( + name: "json_entity_pkey", + table: "json_entity", + column: "crash_report_id"); + + + migrationBuilder.CreateTable( + name: "id_test_entity", + columns: table => new + { + file_id = table.Column(type: "text", nullable: false), + crash_report_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("id_entity_pkey", x => x.file_id); + }); + migrationBuilder.CreateIndex( + name: "id_entity_file_id_idx", + table: "id_test_entity", + column: "file_id"); + migrationBuilder.Sql(""" + INSERT INTO id_test_entity (crash_report_id, file_id) + SELECT ie.crash_report_id, ie.file_id + FROM id_entity ie + """); + + + migrationBuilder.Sql(""" + DELETE FROM id_entity T1 + USING id_entity T2 + WHERE T1.ctid < T2.ctid + AND T1.crash_report_id = T2.crash_report_id; + """); + migrationBuilder.DropPrimaryKey( + name: "PK_id_entity", + table: "id_entity"); + migrationBuilder.DropColumn( + name: "file_id", + table: "id_entity"); + migrationBuilder.DropIndex( + name: "IX_id_entity_crash_report_id", + table: "id_entity"); + migrationBuilder.AddPrimaryKey( + name: "report_entity_pkey", + table: "id_entity", + column: "crash_report_id"); + migrationBuilder.AddColumn( + name: "tenant", + table: "id_entity", + type: "smallint", + nullable: false, + defaultValue: (byte) 0); + + + migrationBuilder.AddForeignKey( + name: "report_entity_html_entity_fkey", + table: "file_entity", + column: "crash_report_id", + principalTable: "id_entity", + principalColumn: "crash_report_id", + onDelete: ReferentialAction.Cascade); + migrationBuilder.AddForeignKey( + name: "report_entity_json_entity_fkey", + table: "json_entity", + column: "crash_report_id", + principalTable: "id_entity", + principalColumn: "crash_report_id", + onDelete: ReferentialAction.Cascade); + + + migrationBuilder.AddForeignKey( + name: "html_entity_id_entity_fkey", + table: "id_test_entity", + column: "crash_report_id", + principalTable: "file_entity", + principalColumn: "crash_report_id", + onDelete: ReferentialAction.Cascade); + migrationBuilder.AddForeignKey( + name: "report_entity_id_entity_fkey", + table: "id_test_entity", + column: "crash_report_id", + principalTable: "id_entity", + principalColumn: "crash_report_id", + onDelete: ReferentialAction.Cascade); + migrationBuilder.AddForeignKey( + name: "json_entity_id_entity_fkey", + table: "id_test_entity", + column: "crash_report_id", + principalTable: "id_entity", + principalColumn: "crash_report_id", + onDelete: ReferentialAction.Cascade); + + + migrationBuilder.RenameTable( + name: "id_entity", + newName: "report_entity"); + migrationBuilder.RenameTable( + name: "file_entity", + newName: "html_entity"); + migrationBuilder.RenameTable( + name: "id_test_entity", + newName: "id_entity"); + } + + protected override void Down(MigrationBuilder migrationBuilder) { } + } +} \ No newline at end of file diff --git a/src/BUTR.CrashReport.Server/Migrations/AppDbContextModelSnapshot.cs b/src/BUTR.CrashReport.Server/Migrations/AppDbContextModelSnapshot.cs index d85247f..5f5ed21 100644 --- a/src/BUTR.CrashReport.Server/Migrations/AppDbContextModelSnapshot.cs +++ b/src/BUTR.CrashReport.Server/Migrations/AppDbContextModelSnapshot.cs @@ -17,35 +17,68 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("ProductVersion", "8.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.FileEntity", b => + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.HtmlEntity", b => { - b.Property("FileId") - .HasColumnType("text") - .HasColumnName("file_id"); + b.Property("CrashReportId") + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); b.Property("DataCompressed") .IsRequired() .HasColumnType("bytea") .HasColumnName("data_compressed"); - b.HasKey("FileId") - .HasName("file_entity_pkey"); + b.HasKey("CrashReportId") + .HasName("html_entity_pkey"); - b.ToTable("file_entity", (string)null); + b.ToTable("html_entity", (string)null); }); modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.IdEntity", b => { + b.Property("CrashReportId") + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); + b.Property("FileId") + .IsRequired() .HasColumnType("text") .HasColumnName("file_id"); + b.HasKey("CrashReportId") + .HasName("id_entity_pkey"); + + b.HasIndex("FileId") + .HasDatabaseName("id_entity_file_id_idx"); + + b.ToTable("id_entity", (string)null); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => + { + b.Property("CrashReportId") + .HasColumnType("uuid") + .HasColumnName("crash_report_id"); + + b.Property("Json") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("data"); + + b.HasKey("CrashReportId"); + + b.ToTable("json_entity", (string)null); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.ReportEntity", b => + { b.Property("CrashReportId") + .ValueGeneratedOnAdd() .HasColumnType("uuid") .HasColumnName("crash_report_id"); @@ -53,53 +86,87 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("timestamp with time zone") .HasColumnName("created"); + b.Property("Tenant") + .HasColumnType("smallint") + .HasColumnName("tenant"); + b.Property("Version") .HasColumnType("smallint") .HasColumnName("version"); - b.HasKey("FileId"); + b.HasKey("CrashReportId") + .HasName("report_entity_pkey"); - b.HasIndex("CrashReportId"); + b.ToTable("report_entity", (string)null); + }); - b.ToTable("id_entity", (string)null); + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.HtmlEntity", b => + { + b.HasOne("BUTR.CrashReport.Server.Models.Database.ReportEntity", "Report") + .WithOne("Html") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.HtmlEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("report_entity_html_entity_fkey"); + + b.Navigation("Report"); }); - modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.IdEntity", b => { - b.Property("FileId") - .HasColumnType("text") - .HasColumnName("file_id"); + b.HasOne("BUTR.CrashReport.Server.Models.Database.HtmlEntity", null) + .WithOne("Id") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.IdEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("html_entity_id_entity_fkey"); - b.Property("CrashReport") + b.HasOne("BUTR.CrashReport.Server.Models.Database.JsonEntity", null) + .WithOne("Id") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.IdEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasColumnType("jsonb") - .HasColumnName("data"); + .HasConstraintName("json_entity_id_entity_fkey"); - b.HasKey("FileId"); + b.HasOne("BUTR.CrashReport.Server.Models.Database.ReportEntity", "Report") + .WithOne("Id") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.IdEntity", "CrashReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("report_entity_id_entity_fkey"); - b.ToTable("json_entity", (string)null); + b.Navigation("Report"); }); - modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.FileEntity", b => + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => { - b.HasOne("BUTR.CrashReport.Server.Models.Database.IdEntity", "Id") - .WithOne() - .HasForeignKey("BUTR.CrashReport.Server.Models.Database.FileEntity", "FileId") + b.HasOne("BUTR.CrashReport.Server.Models.Database.ReportEntity", "Report") + .WithOne("Json") + .HasForeignKey("BUTR.CrashReport.Server.Models.Database.JsonEntity", "CrashReportId") .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .IsRequired() + .HasConstraintName("report_entity_json_entity_fkey"); + + b.Navigation("Report"); + }); + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.HtmlEntity", b => + { b.Navigation("Id"); }); modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.JsonEntity", b => { - b.HasOne("BUTR.CrashReport.Server.Models.Database.IdEntity", "Id") - .WithOne() - .HasForeignKey("BUTR.CrashReport.Server.Models.Database.JsonEntity", "FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.Navigation("Id"); + }); + + modelBuilder.Entity("BUTR.CrashReport.Server.Models.Database.ReportEntity", b => + { + b.Navigation("Html"); b.Navigation("Id"); + + b.Navigation("Json"); }); #pragma warning restore 612, 618 } diff --git a/src/BUTR.CrashReport.Server/Startup.cs b/src/BUTR.CrashReport.Server/Startup.cs index 0a0c3f3..0cc788a 100644 --- a/src/BUTR.CrashReport.Server/Startup.cs +++ b/src/BUTR.CrashReport.Server/Startup.cs @@ -58,7 +58,7 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); //services.AddHostedService(); - services.AddDbContextFactory(x => x.UseNpgsql(_configuration.GetConnectionString("Main"))); + services.AddDbContextFactory(x => x.UseNpgsql(_configuration.GetConnectionString("Main"), y => y.MigrationsAssembly("BUTR.CrashReport.Server"))); services.AddSwaggerGen(opt => {