From 3a1d1d2d46a00749584a1560a98988c02f14546e Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 5 Dec 2024 14:36:41 +0100 Subject: [PATCH 1/7] Remove obsolete file hash --- src/api/BdmsContextExtensions.cs | 1 - .../20241122080247_RemoveFileHash.Designer.cs | 3282 +++++++++++++++++ .../20241122080247_RemoveFileHash.cs | 28 + src/api/Models/File.cs | 6 - 4 files changed, 3310 insertions(+), 7 deletions(-) create mode 100644 src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs create mode 100644 src/api/Migrations/20241122080247_RemoveFileHash.cs diff --git a/src/api/BdmsContextExtensions.cs b/src/api/BdmsContextExtensions.cs index 783ac425c..f5c17fbcf 100644 --- a/src/api/BdmsContextExtensions.cs +++ b/src/api/BdmsContextExtensions.cs @@ -216,7 +216,6 @@ public static void SeedData(this BdmsContext context) .RuleFor(o => o.UpdatedBy, _ => default!) .RuleFor(o => o.Updated, _ => default!) .RuleFor(o => o.Name, f => f.Random.Word()) - .RuleFor(o => o.Hash, f => f.Random.Hash()) .RuleFor(o => o.Type, f => f.Random.Word()) .RuleFor(o => o.Created, f => f.Date.Past().ToUniversalTime().OrNull(f, .05f)) .RuleFor(o => o.NameUuid, f => null); diff --git a/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs b/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs new file mode 100644 index 000000000..222d6a486 --- /dev/null +++ b/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs @@ -0,0 +1,3282 @@ +// +using System; +using BDMS; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace BDMS.Migrations +{ + [DbContext(typeof(BdmsContext))] + [Migration("20241122080247_RemoveFileHash")] + partial class RemoveFileHash + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("bdms") + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree"); + NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis"); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("BDMS.Models.Backfill", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CasingId") + .HasColumnType("integer") + .HasColumnName("casing_id"); + + b.Property("CompletionId") + .HasColumnType("integer") + .HasColumnName("completion_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("from_depth"); + + b.Property("IsOpenBorehole") + .HasColumnType("boolean") + .HasColumnName("is_open_borehole"); + + b.Property("KindId") + .HasColumnType("integer") + .HasColumnName("kind_id"); + + b.Property("MaterialId") + .HasColumnType("integer") + .HasColumnName("material_id"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("to_depth"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CasingId"); + + b.HasIndex("CompletionId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("KindId"); + + b.HasIndex("MaterialId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("backfill", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Borehole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_bho"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AlternateName") + .HasColumnType("text") + .HasColumnName("alternate_name_bho"); + + b.Property("Canton") + .HasColumnType("text") + .HasColumnName("canton_bho"); + + b.Property("ChronostratigraphyId") + .HasColumnType("integer") + .HasColumnName("chronostrat_id_cli"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country_bho"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_bho"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("created_by_bho"); + + b.Property("ElevationPrecisionId") + .HasColumnType("integer") + .HasColumnName("qt_elevation_id_cli"); + + b.Property("ElevationZ") + .HasColumnType("double precision") + .HasColumnName("elevation_z_bho"); + + b.Property("Geometry") + .HasColumnType("geometry") + .HasColumnName("geom_bho"); + + b.Property("HasGroundwater") + .HasColumnType("boolean") + .HasColumnName("groundwater_bho"); + + b.Property("HrsId") + .HasColumnType("integer") + .HasColumnName("hrs_id_cli"); + + b.Property("IsPublic") + .HasColumnType("boolean") + .HasColumnName("public_bho"); + + b.Property("LithologyTopBedrockId") + .HasColumnType("integer") + .HasColumnName("lithology_top_bedrock_id_cli"); + + b.Property("LithostratigraphyId") + .HasColumnType("integer") + .HasColumnName("lithostrat_id_cli"); + + b.Property("LocationPrecisionId") + .HasColumnType("integer") + .HasColumnName("qt_location_id_cli"); + + b.Property("LocationX") + .HasColumnType("double precision") + .HasColumnName("location_x_bho"); + + b.Property("LocationXLV03") + .HasColumnType("double precision") + .HasColumnName("location_x_lv03_bho"); + + b.Property("LocationY") + .HasColumnType("double precision") + .HasColumnName("location_y_bho"); + + b.Property("LocationYLV03") + .HasColumnType("double precision") + .HasColumnName("location_y_lv03_bho"); + + b.Property("Locked") + .HasColumnType("timestamp with time zone") + .HasColumnName("locked_bho"); + + b.Property("LockedById") + .HasColumnType("integer") + .HasColumnName("locked_by_bho"); + + b.Property("Municipality") + .HasColumnType("text") + .HasColumnName("municipality_bho"); + + b.Property("NationalInterest") + .HasColumnType("boolean") + .HasColumnName("national_interest"); + + b.Property("OriginalName") + .HasColumnType("text") + .HasColumnName("original_name_bho"); + + b.Property("OriginalReferenceSystem") + .HasColumnType("integer") + .HasColumnName("srs_id_cli"); + + b.Property("PrecisionLocationX") + .HasColumnType("integer") + .HasColumnName("precision_location_x"); + + b.Property("PrecisionLocationXLV03") + .HasColumnType("integer") + .HasColumnName("precision_location_x_lv03"); + + b.Property("PrecisionLocationY") + .HasColumnType("integer") + .HasColumnName("precision_location_y"); + + b.Property("PrecisionLocationYLV03") + .HasColumnType("integer") + .HasColumnName("precision_location_y_lv03"); + + b.Property("ProjectName") + .HasColumnType("text") + .HasColumnName("project_name_bho"); + + b.Property("PurposeId") + .HasColumnType("integer") + .HasColumnName("purpose_id_cli"); + + b.Property("QtDepthId") + .HasColumnType("integer") + .HasColumnName("qt_depth_id_cli"); + + b.Property("QtReferenceElevationId") + .HasColumnType("integer") + .HasColumnName("qt_reference_elevation_id_cli"); + + b.Property("ReferenceElevation") + .HasColumnType("double precision") + .HasColumnName("reference_elevation_bho"); + + b.Property("ReferenceElevationTypeId") + .HasColumnType("integer") + .HasColumnName("reference_elevation_type_id_cli"); + + b.Property("Remarks") + .HasColumnType("text") + .HasColumnName("remarks_bho"); + + b.Property("RestrictionId") + .HasColumnType("integer") + .HasColumnName("restriction_id_cli"); + + b.Property("RestrictionUntil") + .HasColumnType("timestamp with time zone") + .HasColumnName("restriction_until_bho"); + + b.Property("StatusId") + .HasColumnType("integer") + .HasColumnName("status_id_cli"); + + b.Property("TopBedrockFreshMd") + .HasColumnType("double precision") + .HasColumnName("top_bedrock_fresh_md"); + + b.Property("TopBedrockWeatheredMd") + .HasColumnType("double precision") + .HasColumnName("top_bedrock_weathered_md"); + + b.Property("TotalDepth") + .HasColumnType("double precision") + .HasColumnName("total_depth_bho"); + + b.Property("TypeId") + .HasColumnType("integer") + .HasColumnName("borehole_type_id"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_bho"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updated_by_bho"); + + b.Property("WorkgroupId") + .HasColumnType("integer") + .HasColumnName("id_wgp_fk"); + + b.HasKey("Id"); + + b.HasIndex("ChronostratigraphyId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ElevationPrecisionId"); + + b.HasIndex("HrsId"); + + b.HasIndex("LithologyTopBedrockId"); + + b.HasIndex("LithostratigraphyId"); + + b.HasIndex("LocationPrecisionId"); + + b.HasIndex("LockedById"); + + b.HasIndex("PurposeId"); + + b.HasIndex("QtDepthId"); + + b.HasIndex("QtReferenceElevationId"); + + b.HasIndex("ReferenceElevationTypeId"); + + b.HasIndex("RestrictionId"); + + b.HasIndex("StatusId"); + + b.HasIndex("TypeId"); + + b.HasIndex("UpdatedById"); + + b.HasIndex("WorkgroupId"); + + b.ToTable("borehole", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.BoreholeCodelist", b => + { + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("borehole_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("identifier_id"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text") + .HasColumnName("identifier_value"); + + b.HasKey("BoreholeId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("borehole_identifiers_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.BoreholeFile", b => + { + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("id_bho_fk"); + + b.Property("FileId") + .HasColumnType("integer") + .HasColumnName("id_fil_fk"); + + b.Property("Attached") + .HasColumnType("timestamp with time zone") + .HasColumnName("attached_bfi"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_bfi"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("created_by_bfi"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description_bfi"); + + b.Property("Public") + .HasColumnType("boolean") + .HasColumnName("public_bfi"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update_bfi"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater_bfi"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("id_usr_fk"); + + b.HasKey("BoreholeId", "FileId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("FileId"); + + b.HasIndex("UpdatedById"); + + b.HasIndex("UserId"); + + b.ToTable("borehole_files", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.BoreholeGeometryElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("borehole_id"); + + b.Property("DEVI") + .HasColumnType("double precision"); + + b.Property("HAZI") + .HasColumnType("double precision"); + + b.Property("MD") + .HasColumnType("double precision"); + + b.Property("X") + .HasColumnType("double precision"); + + b.Property("Y") + .HasColumnType("double precision"); + + b.Property("Z") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("BoreholeId"); + + b.ToTable("borehole_geometry", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Casing", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CompletionId") + .HasColumnType("integer") + .HasColumnName("completion_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("DateFinish") + .HasColumnType("date") + .HasColumnName("date_finish"); + + b.Property("DateStart") + .HasColumnType("date") + .HasColumnName("date_start"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CompletionId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("casing", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.CasingElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CasingId") + .HasColumnType("integer") + .HasColumnName("casing_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("from_depth"); + + b.Property("InnerDiameter") + .HasColumnType("double precision") + .HasColumnName("inner_diameter"); + + b.Property("KindId") + .HasColumnType("integer") + .HasColumnName("kind_id"); + + b.Property("MaterialId") + .HasColumnType("integer") + .HasColumnName("material_id"); + + b.Property("OuterDiameter") + .HasColumnType("double precision") + .HasColumnName("outer_diameter"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("to_depth"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CasingId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("KindId"); + + b.HasIndex("MaterialId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("casing_element", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.ChronostratigraphyLayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_chr"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ChronostratigraphyId") + .HasColumnType("integer") + .HasColumnName("chronostratigraphy_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("depth_from"); + + b.Property("StratigraphyId") + .HasColumnType("integer") + .HasColumnName("id_sty_fk"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("depth_to"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("ChronostratigraphyId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("StratigraphyId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("chronostratigraphy", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Codelist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_cli"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasColumnType("text") + .HasColumnName("code_cli"); + + b.Property("Conf") + .HasColumnType("text") + .HasColumnName("conf_cli"); + + b.Property("De") + .HasColumnType("text") + .HasColumnName("text_cli_de"); + + b.Property("En") + .IsRequired() + .HasColumnType("text") + .HasColumnName("text_cli_en"); + + b.Property("Fr") + .HasColumnType("text") + .HasColumnName("text_cli_fr"); + + b.Property("Geolcode") + .HasColumnType("integer") + .HasColumnName("geolcode"); + + b.Property("IsDefault") + .HasColumnType("boolean") + .HasColumnName("default_cli"); + + b.Property("It") + .HasColumnType("text") + .HasColumnName("text_cli_it"); + + b.Property("Order") + .HasColumnType("integer") + .HasColumnName("order_cli"); + + b.Property("Path") + .HasColumnType("ltree") + .HasColumnName("path_cli"); + + b.Property("Ro") + .HasColumnType("text") + .HasColumnName("text_cli_ro"); + + b.Property("Schema") + .HasColumnType("text") + .HasColumnName("schema_cli"); + + b.HasKey("Id"); + + b.ToTable("codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Completion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AbandonDate") + .HasColumnType("date") + .HasColumnName("abandon_date"); + + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("borehole_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("IsPrimary") + .HasColumnType("boolean") + .HasColumnName("is_primary"); + + b.Property("KindId") + .HasColumnType("integer") + .HasColumnName("kind_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("BoreholeId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("KindId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("completion", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Config", b => + { + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name_cfg"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value_cfg"); + + b.HasKey("Name"); + + b.ToTable("config", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.FaciesDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_fac"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("DescriptionQualityId") + .HasColumnType("integer") + .HasColumnName("qt_description_id"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("depth_from"); + + b.Property("StratigraphyId") + .HasColumnType("integer") + .HasColumnName("id_sty_fk"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("depth_to"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DescriptionQualityId"); + + b.HasIndex("StratigraphyId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("facies_description", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.FieldMeasurementResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("FieldMeasurementId") + .HasColumnType("integer") + .HasColumnName("fieldmeasurement_id"); + + b.Property("ParameterId") + .HasColumnType("integer") + .HasColumnName("parameter"); + + b.Property("SampleTypeId") + .HasColumnType("integer") + .HasColumnName("sample_type"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.Property("Value") + .HasColumnType("double precision") + .HasColumnName("value"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("FieldMeasurementId"); + + b.HasIndex("ParameterId"); + + b.HasIndex("SampleTypeId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("fieldmeasurement_result", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_fil"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("uploaded_fil"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("id_usr_fk"); + + b.Property("Hash") + .IsRequired() + .HasColumnType("text") + .HasColumnName("hash_fil"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name_fil"); + + b.Property("NameUuid") + .HasColumnType("text") + .HasColumnName("name_uuid_fil"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text") + .HasColumnName("type_fil"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_fil"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updated_by_fil"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("files", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestEvaluationMethodCode", b => + { + b.Property("HydrotestId") + .HasColumnType("integer") + .HasColumnName("hydrotest_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("codelist_id"); + + b.HasKey("HydrotestId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("hydrotest_evaluationmethod_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestFlowDirectionCode", b => + { + b.Property("HydrotestId") + .HasColumnType("integer") + .HasColumnName("hydrotest_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("codelist_id"); + + b.HasKey("HydrotestId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("hydrotest_flowdirection_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestKindCode", b => + { + b.Property("HydrotestId") + .HasColumnType("integer") + .HasColumnName("hydrotest_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("codelist_id"); + + b.HasKey("HydrotestId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("hydrotest_kind_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("HydrotestId") + .HasColumnType("integer") + .HasColumnName("hydrotest_id"); + + b.Property("MaxValue") + .HasColumnType("double precision") + .HasColumnName("max_value"); + + b.Property("MinValue") + .HasColumnType("double precision") + .HasColumnName("min_value"); + + b.Property("ParameterId") + .HasColumnType("integer") + .HasColumnName("parameter"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.Property("Value") + .HasColumnType("double precision") + .HasColumnName("value"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("HydrotestId"); + + b.HasIndex("ParameterId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("hydrotest_result", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Instrumentation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CasingId") + .HasColumnType("integer") + .HasColumnName("casing_id"); + + b.Property("CompletionId") + .HasColumnType("integer") + .HasColumnName("completion_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("from_depth"); + + b.Property("IsOpenBorehole") + .HasColumnType("boolean") + .HasColumnName("is_open_borehole"); + + b.Property("KindId") + .HasColumnType("integer") + .HasColumnName("kind_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes"); + + b.Property("StatusId") + .HasColumnType("integer") + .HasColumnName("status_id"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("to_depth"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CasingId"); + + b.HasIndex("CompletionId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("KindId"); + + b.HasIndex("StatusId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("instrumentation", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Layer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_lay"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AlterationId") + .HasColumnType("integer") + .HasColumnName("alteration_id_cli"); + + b.Property("CohesionId") + .HasColumnType("integer") + .HasColumnName("cohesion_id_cli"); + + b.Property("CompactnessId") + .HasColumnType("integer") + .HasColumnName("compactness_id_cli"); + + b.Property("ConsistanceId") + .HasColumnType("integer") + .HasColumnName("consistance_id_cli"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation_lay"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator_lay"); + + b.Property("DescriptionQualityId") + .HasColumnType("integer") + .HasColumnName("qt_description_id_cli"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("depth_from_lay"); + + b.Property("GradationId") + .HasColumnType("integer") + .HasColumnName("gradation_id_cli"); + + b.Property("GrainSize1Id") + .HasColumnType("integer") + .HasColumnName("grain_size_1_id_cli"); + + b.Property("GrainSize2Id") + .HasColumnType("integer") + .HasColumnName("grain_size_2_id_cli"); + + b.Property("HumidityId") + .HasColumnType("integer") + .HasColumnName("humidity_id_cli"); + + b.Property("IsLast") + .HasColumnType("boolean") + .HasColumnName("last_lay"); + + b.Property("IsStriae") + .HasColumnType("boolean") + .HasColumnName("striae_lay"); + + b.Property("IsUndefined") + .HasColumnType("boolean") + .HasColumnName("undefined_lay"); + + b.Property("LithologyId") + .HasColumnType("integer") + .HasColumnName("lithology_id_cli"); + + b.Property("LithologyTopBedrockId") + .HasColumnType("integer") + .HasColumnName("lithology_top_bedrock_id_cli"); + + b.Property("LithostratigraphyId") + .HasColumnType("integer") + .HasColumnName("lithostratigraphy_id_cli"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes_lay"); + + b.Property("OriginalLithology") + .HasColumnType("text") + .HasColumnName("original_lithology"); + + b.Property("OriginalUscs") + .HasColumnType("text") + .HasColumnName("uscs_original_lay"); + + b.Property("PlasticityId") + .HasColumnType("integer") + .HasColumnName("plasticity_id_cli"); + + b.Property("StratigraphyId") + .HasColumnType("integer") + .HasColumnName("id_sty_fk"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("depth_to_lay"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update_lay"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater_lay"); + + b.Property("Uscs1Id") + .HasColumnType("integer") + .HasColumnName("uscs_1_id_cli"); + + b.Property("Uscs2Id") + .HasColumnType("integer") + .HasColumnName("uscs_2_id_cli"); + + b.Property("UscsDeterminationId") + .HasColumnType("integer") + .HasColumnName("uscs_determination_id_cli"); + + b.HasKey("Id"); + + b.HasIndex("AlterationId"); + + b.HasIndex("CohesionId"); + + b.HasIndex("CompactnessId"); + + b.HasIndex("ConsistanceId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DescriptionQualityId"); + + b.HasIndex("GradationId"); + + b.HasIndex("GrainSize1Id"); + + b.HasIndex("GrainSize2Id"); + + b.HasIndex("HumidityId"); + + b.HasIndex("LithologyId"); + + b.HasIndex("LithologyTopBedrockId"); + + b.HasIndex("LithostratigraphyId"); + + b.HasIndex("PlasticityId"); + + b.HasIndex("StratigraphyId"); + + b.HasIndex("UpdatedById"); + + b.HasIndex("Uscs1Id"); + + b.HasIndex("Uscs2Id"); + + b.HasIndex("UscsDeterminationId"); + + b.ToTable("layer", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LayerColorCode", b => + { + b.Property("LayerId") + .HasColumnType("integer") + .HasColumnName("layer_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("color_id"); + + b.HasKey("LayerId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("layer_color_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LayerDebrisCode", b => + { + b.Property("LayerId") + .HasColumnType("integer") + .HasColumnName("layer_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("debris_id"); + + b.HasKey("LayerId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("layer_debris_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LayerGrainAngularityCode", b => + { + b.Property("LayerId") + .HasColumnType("integer") + .HasColumnName("layer_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("grain_angularity_id"); + + b.HasKey("LayerId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("layer_grain_angularity_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LayerGrainShapeCode", b => + { + b.Property("LayerId") + .HasColumnType("integer") + .HasColumnName("layer_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("grain_shape_id"); + + b.HasKey("LayerId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("layer_grain_shape_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LayerOrganicComponentCode", b => + { + b.Property("LayerId") + .HasColumnType("integer") + .HasColumnName("layer_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("organic_components_id"); + + b.HasKey("LayerId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("layer_organic_component_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LayerUscs3Code", b => + { + b.Property("LayerId") + .HasColumnType("integer") + .HasColumnName("layer_id"); + + b.Property("CodelistId") + .HasColumnType("integer") + .HasColumnName("uscs3_id"); + + b.HasKey("LayerId", "CodelistId"); + + b.HasIndex("CodelistId"); + + b.ToTable("layer_uscs3_codelist", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LithologicalDescription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_ldp"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("DescriptionQualityId") + .HasColumnType("integer") + .HasColumnName("qt_description_id"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("depth_from"); + + b.Property("StratigraphyId") + .HasColumnType("integer") + .HasColumnName("id_sty_fk"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("depth_to"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("DescriptionQualityId"); + + b.HasIndex("StratigraphyId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("lithological_description", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.LithostratigraphyLayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("depth_from"); + + b.Property("LithostratigraphyId") + .HasColumnType("integer") + .HasColumnName("lithostratigraphy_id"); + + b.Property("StratigraphyId") + .HasColumnType("integer") + .HasColumnName("stratigraphy_id"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("depth_to"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("LithostratigraphyId"); + + b.HasIndex("StratigraphyId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("lithostratigraphy", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Observation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("borehole_id"); + + b.Property("CasingId") + .HasColumnType("integer") + .HasColumnName("casing_id"); + + b.Property("Comment") + .HasColumnType("text") + .HasColumnName("comment"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("Duration") + .HasColumnType("double precision") + .HasColumnName("duration"); + + b.Property("EndTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("end_time"); + + b.Property("FromDepthM") + .HasColumnType("double precision") + .HasColumnName("from_depth_m"); + + b.Property("FromDepthMasl") + .HasColumnType("double precision") + .HasColumnName("from_depth_masl"); + + b.Property("IsOpenBorehole") + .HasColumnType("boolean") + .HasColumnName("is_open_borehole"); + + b.Property("ReliabilityId") + .HasColumnType("integer") + .HasColumnName("reliability"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone") + .HasColumnName("start_time"); + + b.Property("ToDepthM") + .HasColumnType("double precision") + .HasColumnName("to_depth_m"); + + b.Property("ToDepthMasl") + .HasColumnType("double precision") + .HasColumnName("to_depth_masl"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("observation_type"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("BoreholeId"); + + b.HasIndex("CasingId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("ReliabilityId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("observation", "bdms"); + + b.UseTptMappingStrategy(); + }); + + modelBuilder.Entity("BDMS.Models.Section", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("borehole_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("BoreholeId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("UpdatedById"); + + b.ToTable("section", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.SectionElement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("creator"); + + b.Property("CuttingsId") + .HasColumnType("integer") + .HasColumnName("cuttings_id"); + + b.Property("DrillingCoreDiameter") + .HasColumnType("double precision") + .HasColumnName("drilling_core_diameter"); + + b.Property("DrillingDiameter") + .HasColumnType("double precision") + .HasColumnName("drilling_diameter"); + + b.Property("DrillingEndDate") + .HasColumnType("date") + .HasColumnName("drilling_end_date"); + + b.Property("DrillingMethodId") + .HasColumnType("integer") + .HasColumnName("drilling_method_id"); + + b.Property("DrillingMudSubtypeId") + .HasColumnType("integer") + .HasColumnName("mud_subtype_id"); + + b.Property("DrillingMudTypeId") + .HasColumnType("integer") + .HasColumnName("mud_type_id"); + + b.Property("DrillingStartDate") + .HasColumnType("date") + .HasColumnName("drilling_start_date"); + + b.Property("FromDepth") + .HasColumnType("double precision") + .HasColumnName("from_depth"); + + b.Property("Order") + .HasColumnType("integer") + .HasColumnName("order"); + + b.Property("SectionId") + .HasColumnType("integer") + .HasColumnName("section_id"); + + b.Property("ToDepth") + .HasColumnType("double precision") + .HasColumnName("to_depth"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater"); + + b.HasKey("Id"); + + b.HasIndex("CreatedById"); + + b.HasIndex("CuttingsId"); + + b.HasIndex("DrillingMethodId"); + + b.HasIndex("DrillingMudSubtypeId"); + + b.HasIndex("DrillingMudTypeId"); + + b.HasIndex("SectionId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("section_element", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Stratigraphy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_sty"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("id_bho_fk"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation_sty"); + + b.Property("CreatedById") + .HasColumnType("integer") + .HasColumnName("author_sty"); + + b.Property("Date") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_sty"); + + b.Property("IsPrimary") + .HasColumnType("boolean") + .HasColumnName("primary_sty"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name_sty"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes_sty"); + + b.Property("QualityId") + .HasColumnType("integer") + .HasColumnName("quality_id"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("update_sty"); + + b.Property("UpdatedById") + .HasColumnType("integer") + .HasColumnName("updater_sty"); + + b.HasKey("Id"); + + b.HasIndex("BoreholeId"); + + b.HasIndex("CreatedById"); + + b.HasIndex("QualityId"); + + b.HasIndex("UpdatedById"); + + b.ToTable("stratigraphy", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Term", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_tes"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Creation") + .HasColumnType("timestamp with time zone") + .HasColumnName("creation_tes"); + + b.Property("Expiration") + .HasColumnType("timestamp with time zone") + .HasColumnName("expired_tes"); + + b.Property("IsDraft") + .HasColumnType("boolean") + .HasColumnName("draft_tes"); + + b.Property("TextDe") + .HasColumnType("text") + .HasColumnName("text_tes_de"); + + b.Property("TextEn") + .IsRequired() + .HasColumnType("text") + .HasColumnName("text_tes_en"); + + b.Property("TextFr") + .HasColumnType("text") + .HasColumnName("text_tes_fr"); + + b.Property("TextIt") + .HasColumnType("text") + .HasColumnName("text_tes_it"); + + b.Property("TextRo") + .HasColumnType("text") + .HasColumnName("text_tes_ro"); + + b.HasKey("Id"); + + b.ToTable("terms", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.TermsAccepted", b => + { + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("id_usr_fk"); + + b.Property("TermId") + .HasColumnType("integer") + .HasColumnName("id_tes_fk"); + + b.Property("AcceptedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("accepted_tea"); + + b.HasKey("UserId", "TermId"); + + b.HasIndex("TermId"); + + b.ToTable("terms_accepted", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_usr"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_usr"); + + b.Property("DisabledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("disabled_usr"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("firstname"); + + b.Property("IsAdmin") + .HasColumnType("boolean") + .HasColumnName("admin_usr"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("lastname"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("username"); + + b.Property("Settings") + .HasColumnType("text") + .HasColumnName("settings_usr"); + + b.Property("SubjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("subject_id"); + + b.HasKey("Id"); + + b.ToTable("users", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.UserWorkgroupRole", b => + { + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("id_usr_fk"); + + b.Property("WorkgroupId") + .HasColumnType("integer") + .HasColumnName("id_wgp_fk"); + + b.Property("Role") + .HasColumnType("int") + .HasColumnName("id_rol_fk"); + + b.HasKey("UserId", "WorkgroupId", "Role"); + + b.HasIndex("WorkgroupId"); + + b.ToTable("users_roles", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Workflow", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_wkf"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BoreholeId") + .HasColumnType("integer") + .HasColumnName("id_bho_fk"); + + b.Property("Finished") + .HasColumnType("timestamp with time zone") + .HasColumnName("finished_wkf"); + + b.Property("Notes") + .HasColumnType("text") + .HasColumnName("notes_wkf"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("id_rol_fk"); + + b.Property("Started") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_wkf"); + + b.Property("UserId") + .HasColumnType("integer") + .HasColumnName("id_usr_fk"); + + b.HasKey("Id"); + + b.HasIndex("BoreholeId"); + + b.HasIndex("UserId"); + + b.ToTable("workflow", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Workgroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id_wgp"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_wgp"); + + b.Property("DisabledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("disabled_wgp"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name_wgp"); + + b.Property("Settings") + .HasColumnType("json") + .HasColumnName("settings_wgp"); + + b.HasKey("Id"); + + b.ToTable("workgroups", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.FieldMeasurement", b => + { + b.HasBaseType("BDMS.Models.Observation"); + + b.ToTable("field_measurement", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.GroundwaterLevelMeasurement", b => + { + b.HasBaseType("BDMS.Models.Observation"); + + b.Property("KindId") + .HasColumnType("integer") + .HasColumnName("kind"); + + b.Property("LevelM") + .HasColumnType("double precision") + .HasColumnName("level_m"); + + b.Property("LevelMasl") + .HasColumnType("double precision") + .HasColumnName("level_masl"); + + b.HasIndex("KindId"); + + b.ToTable("groundwater_level_measurement", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Hydrotest", b => + { + b.HasBaseType("BDMS.Models.Observation"); + + b.ToTable("hydrotest", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.WaterIngress", b => + { + b.HasBaseType("BDMS.Models.Observation"); + + b.Property("ConditionsId") + .HasColumnType("integer") + .HasColumnName("conditions"); + + b.Property("QuantityId") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.HasIndex("ConditionsId"); + + b.HasIndex("QuantityId"); + + b.ToTable("water_ingress", "bdms"); + }); + + modelBuilder.Entity("BDMS.Models.Backfill", b => + { + b.HasOne("BDMS.Models.Casing", "Casing") + .WithMany("Backfills") + .HasForeignKey("CasingId"); + + b.HasOne("BDMS.Models.Completion", "Completion") + .WithMany("Backfills") + .HasForeignKey("CompletionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Kind") + .WithMany() + .HasForeignKey("KindId"); + + b.HasOne("BDMS.Models.Codelist", "Material") + .WithMany() + .HasForeignKey("MaterialId"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Casing"); + + b.Navigation("Completion"); + + b.Navigation("CreatedBy"); + + b.Navigation("Kind"); + + b.Navigation("Material"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Borehole", b => + { + b.HasOne("BDMS.Models.Codelist", "Chronostratigraphy") + .WithMany() + .HasForeignKey("ChronostratigraphyId"); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "ElevationPrecision") + .WithMany() + .HasForeignKey("ElevationPrecisionId"); + + b.HasOne("BDMS.Models.Codelist", "Hrs") + .WithMany() + .HasForeignKey("HrsId"); + + b.HasOne("BDMS.Models.Codelist", "LithologyTopBedrock") + .WithMany() + .HasForeignKey("LithologyTopBedrockId"); + + b.HasOne("BDMS.Models.Codelist", "Lithostratigraphy") + .WithMany() + .HasForeignKey("LithostratigraphyId"); + + b.HasOne("BDMS.Models.Codelist", "LocationPrecision") + .WithMany() + .HasForeignKey("LocationPrecisionId"); + + b.HasOne("BDMS.Models.User", "LockedBy") + .WithMany() + .HasForeignKey("LockedById"); + + b.HasOne("BDMS.Models.Codelist", "Purpose") + .WithMany() + .HasForeignKey("PurposeId"); + + b.HasOne("BDMS.Models.Codelist", "QtDepth") + .WithMany() + .HasForeignKey("QtDepthId"); + + b.HasOne("BDMS.Models.Codelist", "QtReferenceElevation") + .WithMany() + .HasForeignKey("QtReferenceElevationId"); + + b.HasOne("BDMS.Models.Codelist", "ReferenceElevationType") + .WithMany() + .HasForeignKey("ReferenceElevationTypeId"); + + b.HasOne("BDMS.Models.Codelist", "Restriction") + .WithMany() + .HasForeignKey("RestrictionId"); + + b.HasOne("BDMS.Models.Codelist", "Status") + .WithMany() + .HasForeignKey("StatusId"); + + b.HasOne("BDMS.Models.Codelist", "Type") + .WithMany() + .HasForeignKey("TypeId"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.HasOne("BDMS.Models.Workgroup", "Workgroup") + .WithMany("Boreholes") + .HasForeignKey("WorkgroupId"); + + b.Navigation("Chronostratigraphy"); + + b.Navigation("CreatedBy"); + + b.Navigation("ElevationPrecision"); + + b.Navigation("Hrs"); + + b.Navigation("LithologyTopBedrock"); + + b.Navigation("Lithostratigraphy"); + + b.Navigation("LocationPrecision"); + + b.Navigation("LockedBy"); + + b.Navigation("Purpose"); + + b.Navigation("QtDepth"); + + b.Navigation("QtReferenceElevation"); + + b.Navigation("ReferenceElevationType"); + + b.Navigation("Restriction"); + + b.Navigation("Status"); + + b.Navigation("Type"); + + b.Navigation("UpdatedBy"); + + b.Navigation("Workgroup"); + }); + + modelBuilder.Entity("BDMS.Models.BoreholeCodelist", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("BoreholeCodelists") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("BoreholeCodelists") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borehole"); + + b.Navigation("Codelist"); + }); + + modelBuilder.Entity("BDMS.Models.BoreholeFile", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("BoreholeFiles") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.File", "File") + .WithMany("BoreholeFiles") + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.HasOne("BDMS.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Borehole"); + + b.Navigation("CreatedBy"); + + b.Navigation("File"); + + b.Navigation("UpdatedBy"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BDMS.Models.BoreholeGeometryElement", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("BoreholeGeometry") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borehole"); + }); + + modelBuilder.Entity("BDMS.Models.Casing", b => + { + b.HasOne("BDMS.Models.Completion", "Completion") + .WithMany("Casings") + .HasForeignKey("CompletionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Completion"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.CasingElement", b => + { + b.HasOne("BDMS.Models.Casing", "Casing") + .WithMany("CasingElements") + .HasForeignKey("CasingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Kind") + .WithMany() + .HasForeignKey("KindId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "Material") + .WithMany() + .HasForeignKey("MaterialId"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Casing"); + + b.Navigation("CreatedBy"); + + b.Navigation("Kind"); + + b.Navigation("Material"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.ChronostratigraphyLayer", b => + { + b.HasOne("BDMS.Models.Codelist", "Chronostratigraphy") + .WithMany() + .HasForeignKey("ChronostratigraphyId"); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Stratigraphy", "Stratigraphy") + .WithMany("ChronostratigraphyLayers") + .HasForeignKey("StratigraphyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Chronostratigraphy"); + + b.Navigation("CreatedBy"); + + b.Navigation("Stratigraphy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Completion", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("Completions") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Kind") + .WithMany() + .HasForeignKey("KindId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Borehole"); + + b.Navigation("CreatedBy"); + + b.Navigation("Kind"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.FaciesDescription", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "DescriptionQuality") + .WithMany() + .HasForeignKey("DescriptionQualityId"); + + b.HasOne("BDMS.Models.Stratigraphy", "Stratigraphy") + .WithMany("FaciesDescriptions") + .HasForeignKey("StratigraphyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("DescriptionQuality"); + + b.Navigation("Stratigraphy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.FieldMeasurementResult", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.FieldMeasurement", "FieldMeasurement") + .WithMany("FieldMeasurementResults") + .HasForeignKey("FieldMeasurementId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "Parameter") + .WithMany() + .HasForeignKey("ParameterId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "SampleType") + .WithMany() + .HasForeignKey("SampleTypeId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("FieldMeasurement"); + + b.Navigation("Parameter"); + + b.Navigation("SampleType"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.File", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestEvaluationMethodCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("HydrotestEvaluationMethodCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Hydrotest", "Hydrotest") + .WithMany("HydrotestEvaluationMethodCodes") + .HasForeignKey("HydrotestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Hydrotest"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestFlowDirectionCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("HydrotestFlowDirectionCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Hydrotest", "Hydrotest") + .WithMany("HydrotestFlowDirectionCodes") + .HasForeignKey("HydrotestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Hydrotest"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestKindCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("HydrotestKindCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Hydrotest", "Hydrotest") + .WithMany("HydrotestKindCodes") + .HasForeignKey("HydrotestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Hydrotest"); + }); + + modelBuilder.Entity("BDMS.Models.HydrotestResult", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Hydrotest", "Hydrotest") + .WithMany("HydrotestResults") + .HasForeignKey("HydrotestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "Parameter") + .WithMany() + .HasForeignKey("ParameterId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Hydrotest"); + + b.Navigation("Parameter"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Instrumentation", b => + { + b.HasOne("BDMS.Models.Casing", "Casing") + .WithMany("Instrumentations") + .HasForeignKey("CasingId"); + + b.HasOne("BDMS.Models.Completion", "Completion") + .WithMany("Instrumentations") + .HasForeignKey("CompletionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Kind") + .WithMany() + .HasForeignKey("KindId"); + + b.HasOne("BDMS.Models.Codelist", "Status") + .WithMany() + .HasForeignKey("StatusId"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Casing"); + + b.Navigation("Completion"); + + b.Navigation("CreatedBy"); + + b.Navigation("Kind"); + + b.Navigation("Status"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Layer", b => + { + b.HasOne("BDMS.Models.Codelist", "Alteration") + .WithMany() + .HasForeignKey("AlterationId"); + + b.HasOne("BDMS.Models.Codelist", "Cohesion") + .WithMany() + .HasForeignKey("CohesionId"); + + b.HasOne("BDMS.Models.Codelist", "Compactness") + .WithMany() + .HasForeignKey("CompactnessId"); + + b.HasOne("BDMS.Models.Codelist", "Consistance") + .WithMany() + .HasForeignKey("ConsistanceId"); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "DescriptionQuality") + .WithMany() + .HasForeignKey("DescriptionQualityId"); + + b.HasOne("BDMS.Models.Codelist", "Gradation") + .WithMany() + .HasForeignKey("GradationId"); + + b.HasOne("BDMS.Models.Codelist", "GrainSize1") + .WithMany() + .HasForeignKey("GrainSize1Id"); + + b.HasOne("BDMS.Models.Codelist", "GrainSize2") + .WithMany() + .HasForeignKey("GrainSize2Id"); + + b.HasOne("BDMS.Models.Codelist", "Humidity") + .WithMany() + .HasForeignKey("HumidityId"); + + b.HasOne("BDMS.Models.Codelist", "Lithology") + .WithMany() + .HasForeignKey("LithologyId"); + + b.HasOne("BDMS.Models.Codelist", "LithologyTopBedrock") + .WithMany() + .HasForeignKey("LithologyTopBedrockId"); + + b.HasOne("BDMS.Models.Codelist", "Lithostratigraphy") + .WithMany() + .HasForeignKey("LithostratigraphyId"); + + b.HasOne("BDMS.Models.Codelist", "Plasticity") + .WithMany() + .HasForeignKey("PlasticityId"); + + b.HasOne("BDMS.Models.Stratigraphy", "Stratigraphy") + .WithMany("Layers") + .HasForeignKey("StratigraphyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.HasOne("BDMS.Models.Codelist", "Uscs1") + .WithMany() + .HasForeignKey("Uscs1Id"); + + b.HasOne("BDMS.Models.Codelist", "Uscs2") + .WithMany() + .HasForeignKey("Uscs2Id"); + + b.HasOne("BDMS.Models.Codelist", "UscsDetermination") + .WithMany() + .HasForeignKey("UscsDeterminationId"); + + b.Navigation("Alteration"); + + b.Navigation("Cohesion"); + + b.Navigation("Compactness"); + + b.Navigation("Consistance"); + + b.Navigation("CreatedBy"); + + b.Navigation("DescriptionQuality"); + + b.Navigation("Gradation"); + + b.Navigation("GrainSize1"); + + b.Navigation("GrainSize2"); + + b.Navigation("Humidity"); + + b.Navigation("Lithology"); + + b.Navigation("LithologyTopBedrock"); + + b.Navigation("Lithostratigraphy"); + + b.Navigation("Plasticity"); + + b.Navigation("Stratigraphy"); + + b.Navigation("UpdatedBy"); + + b.Navigation("Uscs1"); + + b.Navigation("Uscs2"); + + b.Navigation("UscsDetermination"); + }); + + modelBuilder.Entity("BDMS.Models.LayerColorCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("LayerColorCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Layer", "Layer") + .WithMany("LayerColorCodes") + .HasForeignKey("LayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Layer"); + }); + + modelBuilder.Entity("BDMS.Models.LayerDebrisCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("LayerDebrisCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Layer", "Layer") + .WithMany("LayerDebrisCodes") + .HasForeignKey("LayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Layer"); + }); + + modelBuilder.Entity("BDMS.Models.LayerGrainAngularityCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("LayerGrainAngularityCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Layer", "Layer") + .WithMany("LayerGrainAngularityCodes") + .HasForeignKey("LayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Layer"); + }); + + modelBuilder.Entity("BDMS.Models.LayerGrainShapeCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("LayerGrainShapeCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Layer", "Layer") + .WithMany("LayerGrainShapeCodes") + .HasForeignKey("LayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Layer"); + }); + + modelBuilder.Entity("BDMS.Models.LayerOrganicComponentCode", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("LayerOrganicComponentCodes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Layer", "Layer") + .WithMany("LayerOrganicComponentCodes") + .HasForeignKey("LayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Layer"); + }); + + modelBuilder.Entity("BDMS.Models.LayerUscs3Code", b => + { + b.HasOne("BDMS.Models.Codelist", "Codelist") + .WithMany("LayerUscs3Codes") + .HasForeignKey("CodelistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Layer", "Layer") + .WithMany("LayerUscs3Codes") + .HasForeignKey("LayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Codelist"); + + b.Navigation("Layer"); + }); + + modelBuilder.Entity("BDMS.Models.LithologicalDescription", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "DescriptionQuality") + .WithMany() + .HasForeignKey("DescriptionQualityId"); + + b.HasOne("BDMS.Models.Stratigraphy", "Stratigraphy") + .WithMany("LithologicalDescriptions") + .HasForeignKey("StratigraphyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("DescriptionQuality"); + + b.Navigation("Stratigraphy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.LithostratigraphyLayer", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Lithostratigraphy") + .WithMany() + .HasForeignKey("LithostratigraphyId"); + + b.HasOne("BDMS.Models.Stratigraphy", "Stratigraphy") + .WithMany("LithostratigraphyLayers") + .HasForeignKey("StratigraphyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Lithostratigraphy"); + + b.Navigation("Stratigraphy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Observation", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("Observations") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Casing", "Casing") + .WithMany("Observations") + .HasForeignKey("CasingId"); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Reliability") + .WithMany() + .HasForeignKey("ReliabilityId") + .OnDelete(DeleteBehavior.NoAction); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Borehole"); + + b.Navigation("Casing"); + + b.Navigation("CreatedBy"); + + b.Navigation("Reliability"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Section", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("Sections") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Borehole"); + + b.Navigation("CreatedBy"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.SectionElement", b => + { + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Cuttings") + .WithMany() + .HasForeignKey("CuttingsId"); + + b.HasOne("BDMS.Models.Codelist", "DrillingMethod") + .WithMany() + .HasForeignKey("DrillingMethodId"); + + b.HasOne("BDMS.Models.Codelist", "DrillingMudSubtype") + .WithMany() + .HasForeignKey("DrillingMudSubtypeId"); + + b.HasOne("BDMS.Models.Codelist", "DrillingMudType") + .WithMany() + .HasForeignKey("DrillingMudTypeId"); + + b.HasOne("BDMS.Models.Section", "Section") + .WithMany("SectionElements") + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("CreatedBy"); + + b.Navigation("Cuttings"); + + b.Navigation("DrillingMethod"); + + b.Navigation("DrillingMudSubtype"); + + b.Navigation("DrillingMudType"); + + b.Navigation("Section"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.Stratigraphy", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("Stratigraphies") + .HasForeignKey("BoreholeId"); + + b.HasOne("BDMS.Models.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById"); + + b.HasOne("BDMS.Models.Codelist", "Quality") + .WithMany() + .HasForeignKey("QualityId"); + + b.HasOne("BDMS.Models.User", "UpdatedBy") + .WithMany() + .HasForeignKey("UpdatedById"); + + b.Navigation("Borehole"); + + b.Navigation("CreatedBy"); + + b.Navigation("Quality"); + + b.Navigation("UpdatedBy"); + }); + + modelBuilder.Entity("BDMS.Models.TermsAccepted", b => + { + b.HasOne("BDMS.Models.Term", "Term") + .WithMany() + .HasForeignKey("TermId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "User") + .WithMany("TermsAccepted") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Term"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BDMS.Models.UserWorkgroupRole", b => + { + b.HasOne("BDMS.Models.User", "User") + .WithMany("WorkgroupRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Workgroup", "Workgroup") + .WithMany() + .HasForeignKey("WorkgroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("Workgroup"); + }); + + modelBuilder.Entity("BDMS.Models.Workflow", b => + { + b.HasOne("BDMS.Models.Borehole", "Borehole") + .WithMany("Workflows") + .HasForeignKey("BoreholeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Borehole"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("BDMS.Models.FieldMeasurement", b => + { + b.HasOne("BDMS.Models.Observation", null) + .WithOne() + .HasForeignKey("BDMS.Models.FieldMeasurement", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BDMS.Models.GroundwaterLevelMeasurement", b => + { + b.HasOne("BDMS.Models.Observation", null) + .WithOne() + .HasForeignKey("BDMS.Models.GroundwaterLevelMeasurement", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "Kind") + .WithMany() + .HasForeignKey("KindId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Kind"); + }); + + modelBuilder.Entity("BDMS.Models.Hydrotest", b => + { + b.HasOne("BDMS.Models.Observation", null) + .WithOne() + .HasForeignKey("BDMS.Models.Hydrotest", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("BDMS.Models.WaterIngress", b => + { + b.HasOne("BDMS.Models.Codelist", "Conditions") + .WithMany() + .HasForeignKey("ConditionsId"); + + b.HasOne("BDMS.Models.Observation", null) + .WithOne() + .HasForeignKey("BDMS.Models.WaterIngress", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("BDMS.Models.Codelist", "Quantity") + .WithMany() + .HasForeignKey("QuantityId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Conditions"); + + b.Navigation("Quantity"); + }); + + modelBuilder.Entity("BDMS.Models.Borehole", b => + { + b.Navigation("BoreholeCodelists"); + + b.Navigation("BoreholeFiles"); + + b.Navigation("BoreholeGeometry"); + + b.Navigation("Completions"); + + b.Navigation("Observations"); + + b.Navigation("Sections"); + + b.Navigation("Stratigraphies"); + + b.Navigation("Workflows"); + }); + + modelBuilder.Entity("BDMS.Models.Casing", b => + { + b.Navigation("Backfills"); + + b.Navigation("CasingElements"); + + b.Navigation("Instrumentations"); + + b.Navigation("Observations"); + }); + + modelBuilder.Entity("BDMS.Models.Codelist", b => + { + b.Navigation("BoreholeCodelists"); + + b.Navigation("HydrotestEvaluationMethodCodes"); + + b.Navigation("HydrotestFlowDirectionCodes"); + + b.Navigation("HydrotestKindCodes"); + + b.Navigation("LayerColorCodes"); + + b.Navigation("LayerDebrisCodes"); + + b.Navigation("LayerGrainAngularityCodes"); + + b.Navigation("LayerGrainShapeCodes"); + + b.Navigation("LayerOrganicComponentCodes"); + + b.Navigation("LayerUscs3Codes"); + }); + + modelBuilder.Entity("BDMS.Models.Completion", b => + { + b.Navigation("Backfills"); + + b.Navigation("Casings"); + + b.Navigation("Instrumentations"); + }); + + modelBuilder.Entity("BDMS.Models.File", b => + { + b.Navigation("BoreholeFiles"); + }); + + modelBuilder.Entity("BDMS.Models.Layer", b => + { + b.Navigation("LayerColorCodes"); + + b.Navigation("LayerDebrisCodes"); + + b.Navigation("LayerGrainAngularityCodes"); + + b.Navigation("LayerGrainShapeCodes"); + + b.Navigation("LayerOrganicComponentCodes"); + + b.Navigation("LayerUscs3Codes"); + }); + + modelBuilder.Entity("BDMS.Models.Section", b => + { + b.Navigation("SectionElements"); + }); + + modelBuilder.Entity("BDMS.Models.Stratigraphy", b => + { + b.Navigation("ChronostratigraphyLayers"); + + b.Navigation("FaciesDescriptions"); + + b.Navigation("Layers"); + + b.Navigation("LithologicalDescriptions"); + + b.Navigation("LithostratigraphyLayers"); + }); + + modelBuilder.Entity("BDMS.Models.User", b => + { + b.Navigation("TermsAccepted"); + + b.Navigation("WorkgroupRoles"); + }); + + modelBuilder.Entity("BDMS.Models.Workgroup", b => + { + b.Navigation("Boreholes"); + }); + + modelBuilder.Entity("BDMS.Models.FieldMeasurement", b => + { + b.Navigation("FieldMeasurementResults"); + }); + + modelBuilder.Entity("BDMS.Models.Hydrotest", b => + { + b.Navigation("HydrotestEvaluationMethodCodes"); + + b.Navigation("HydrotestFlowDirectionCodes"); + + b.Navigation("HydrotestKindCodes"); + + b.Navigation("HydrotestResults"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/api/Migrations/20241122080247_RemoveFileHash.cs b/src/api/Migrations/20241122080247_RemoveFileHash.cs new file mode 100644 index 000000000..985fd9e6c --- /dev/null +++ b/src/api/Migrations/20241122080247_RemoveFileHash.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BDMS.Migrations; + +/// +public partial class RemoveFileHash : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "hash_fil", + schema: "bdms", + table: "files"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "hash_fil", + schema: "bdms", + table: "files", + type: "text", + nullable: false, + defaultValue: ""); + } +} diff --git a/src/api/Models/File.cs b/src/api/Models/File.cs index 3247924e6..e8489da4c 100644 --- a/src/api/Models/File.cs +++ b/src/api/Models/File.cs @@ -42,12 +42,6 @@ public class File : IChangeTracking, IIdentifyable [Column("name_uuid_fil")] public string? NameUuid { get; set; } - /// - /// Gets or sets the 's hash. - /// - [Column("hash_fil")] - public string Hash { get; set; } - /// /// Gets or sets the 's type. /// From 648b245a53165035646c8c3abf469329eb1eb8b9 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Mon, 2 Dec 2024 13:54:34 +0100 Subject: [PATCH 2/7] Remove deduplication check when adding or detaching borehole attachments. --- src/api/BoreholeFileCloudService.cs | 40 +++++--------- src/api/Controllers/BoreholeFileController.cs | 12 ++--- .../cypress/e2e/detailPage/attachments.cy.js | 34 ++++++------ .../attachments/table/filesTableComponent.jsx | 1 + .../Controllers/BoreholeFileControllerTest.cs | 52 +++++++++---------- 5 files changed, 63 insertions(+), 76 deletions(-) diff --git a/src/api/BoreholeFileCloudService.cs b/src/api/BoreholeFileCloudService.cs index 871dc8bcc..26924a6f8 100644 --- a/src/api/BoreholeFileCloudService.cs +++ b/src/api/BoreholeFileCloudService.cs @@ -35,18 +35,6 @@ public BoreholeFileCloudService(BdmsContext context, IConfiguration configuratio /// The to link the uploaded to. public async Task UploadFileAndLinkToBorehole(IFormFile file, int boreholeId) { - // Generate a hash based on the file content. - var base64Hash = ""; - using (SHA256 sha256Hash = SHA256.Create()) - { - using Stream stream = file.OpenReadStream(); - byte[] hashBytes = await sha256Hash.ComputeHashAsync(stream).ConfigureAwait(false); - base64Hash = Convert.ToBase64String(hashBytes); - } - - // Check any file with the same hash already exists in the database. - var fileId = context.Files.FirstOrDefault(f => f.Hash == base64Hash)?.Id; - // Use transaction to ensure data is only stored to db if the file upload was sucessful. Only create a transaction if there is not already one from the calling method. using var transaction = context.Database.CurrentTransaction == null ? await context.Database.BeginTransactionAsync().ConfigureAwait(false) : null; try @@ -60,28 +48,26 @@ public async Task UploadFileAndLinkToBorehole(IFormFile file, int if (user == null || subjectId == null) throw new InvalidOperationException($"No user with subject_id <{subjectId}> found."); - // If file does not exist on storage, upload it and create file in database. - if (fileId == null) - { - var fileExtension = Path.GetExtension(file.FileName); - var fileNameGuid = $"{Guid.NewGuid()}{fileExtension}"; + // Register the new file in the boreholes database. + var fileExtension = Path.GetExtension(file.FileName); + var fileNameGuid = $"{Guid.NewGuid()}{fileExtension}"; - var bdmsFile = new Models.File { Name = file.FileName, NameUuid = fileNameGuid, Hash = base64Hash, Type = file.ContentType }; + var bdmsFile = new Models.File { Name = file.FileName, NameUuid = fileNameGuid, Type = file.ContentType }; - await context.Files.AddAsync(bdmsFile).ConfigureAwait(false); - await context.UpdateChangeInformationAndSaveChangesAsync(httpContextAccessor.HttpContext!).ConfigureAwait(false); + await context.Files.AddAsync(bdmsFile).ConfigureAwait(false); + await context.UpdateChangeInformationAndSaveChangesAsync(httpContextAccessor.HttpContext!).ConfigureAwait(false); - fileId = bdmsFile.Id; + var fileId = bdmsFile.Id; - // Upload the file to the cloud storage. - await UploadObject(file, fileNameGuid).ConfigureAwait(false); - } + // Upload the file to the cloud storage. + await UploadObject(file, fileNameGuid).ConfigureAwait(false); // If file is already linked to the borehole, throw an exception. - if (context.BoreholeFiles.Any(bf => bf.BoreholeId == boreholeId && bf.FileId == fileId)) throw new InvalidOperationException($"File <{file.FileName}> is already attached to borehole with Id <{boreholeId}>."); + if (await context.BoreholeFiles.AnyAsync(bf => bf.BoreholeId == boreholeId && bf.FileId == fileId).ConfigureAwait(false)) + throw new InvalidOperationException($"File <{file.FileName}> is already attached to borehole with Id <{boreholeId}>."); // Link file to the borehole. - var boreholeFile = new BoreholeFile { FileId = (int)fileId, BoreholeId = boreholeId, UserId = user.Id, Attached = DateTime.UtcNow }; + var boreholeFile = new BoreholeFile { FileId = fileId, BoreholeId = boreholeId, UserId = user.Id, Attached = DateTime.UtcNow }; var entityEntry = await context.BoreholeFiles.AddAsync(boreholeFile).ConfigureAwait(false); await context.UpdateChangeInformationAndSaveChangesAsync(httpContextAccessor.HttpContext!).ConfigureAwait(false); @@ -91,7 +77,7 @@ public async Task UploadFileAndLinkToBorehole(IFormFile file, int } catch (Exception ex) { - logger.LogError(ex, $"Error attaching file <{file.FileName}> to borehole with Id <{boreholeId}>."); + logger.LogError(ex, "Error attaching file <{FileName}> to borehole with Id <{BoreholeId}>.", file.FileName, boreholeId); throw; } } diff --git a/src/api/Controllers/BoreholeFileController.cs b/src/api/Controllers/BoreholeFileController.cs index 11aa1200b..3ad572355 100644 --- a/src/api/Controllers/BoreholeFileController.cs +++ b/src/api/Controllers/BoreholeFileController.cs @@ -210,9 +210,9 @@ public async Task DetachFromBorehole([Required, Range(1, int.MaxV try { // Get the file and its borehole files from the database. - var boreholeFile = await context.BoreholeFiles.Include(f => f.File).FirstOrDefaultAsync(f => f.FileId == boreholeFileId).ConfigureAwait(false); + var boreholeFile = await context.BoreholeFiles.Include(f => f.File).SingleOrDefaultAsync(f => f.FileId == boreholeFileId).ConfigureAwait(false); - if (boreholeFile == null) return NotFound(); + if (boreholeFile == null) return NotFound("Borehole file for the provided boreholeFileId not found."); var fileId = boreholeFile.File.Id; @@ -220,11 +220,9 @@ public async Task DetachFromBorehole([Required, Range(1, int.MaxV context.BoreholeFiles.Remove(boreholeFile); await context.SaveChangesAsync().ConfigureAwait(false); - // Get the file and its borehole files from the database. - var file = await context.Files.Include(f => f.BoreholeFiles).FirstOrDefaultAsync(f => f.Id == fileId).ConfigureAwait(false); - - // If the file is not linked to any boreholes, delete it from the cloud storage and the database. - if (file?.NameUuid != null && file.BoreholeFiles.Count == 0) + // Delete the file from the cloud storage and the database. + var file = await context.Files.SingleOrDefaultAsync(f => f.Id == fileId).ConfigureAwait(false); + if (file?.NameUuid != null) { await boreholeFileCloudService.DeleteObject(file.NameUuid).ConfigureAwait(false); context.Files.Remove(file); diff --git a/src/client/cypress/e2e/detailPage/attachments.cy.js b/src/client/cypress/e2e/detailPage/attachments.cy.js index 2103d41ec..392b4d515 100644 --- a/src/client/cypress/e2e/detailPage/attachments.cy.js +++ b/src/client/cypress/e2e/detailPage/attachments.cy.js @@ -62,44 +62,48 @@ describe("Tests for 'Attachments' edit page.", () => { cy.get("tbody").children().contains("td", "text/plain"); cy.get("tbody").children().contains("td", "application/pdf"); - // Select "IRATETRINITY.pdf" second time. + // Upload and verify file "IRATETRINITY.pdf" for the second time but with different file name. cy.get("input[type=file]").selectFile( { contents: Cypress.Buffer.from(fileContent), - fileName: "IRATETRINITY.pdf", + fileName: "IRATETRINITY_2.pdf", mimeType: "application/pdf", }, { force: true }, ); - - // Upload "IRATETRINITY.pdf" second time. Should not be uploaded. cy.get('[data-cy="attachments-upload-button"]').should("be.visible").click(); cy.wait(["@upload-files"]); - - // Check if error message is displayed. - cy.contains("This file has already been uploaded for this borehole"); - - // Ensure file does not exist in download folder before download. If so, delete it. - deleteDownloadedFile("IRATETRINITY.pdf"); + cy.wait(["@getAllAttachments"]); + cy.get("tbody").children().should("have.length", 3); + cy.get("tbody").children().contains("td", "text/plain"); + cy.get("tbody").children().contains("td", "application/pdf"); // intercept download file request cy.intercept("/api/v2/boreholefile/download?boreholeFileId=**").as("download-file"); + // Ensure file does not exist in download folder before download. If so, delete it. + deleteDownloadedFile("IRATETRINITY_2.pdf"); + // Download recently uploaded file - cy.get("tbody").children().contains("span", "IRATETRINITY.pdf").click(); + cy.get("tbody").children().contains("span", "IRATETRINITY_2.pdf").click(); cy.wait("@download-file"); - // Check if file is present in download folder. - readDownloadedFile("IRATETRINITY.pdf"); + // Check if the file is present in download folder. + readDownloadedFile("IRATETRINITY_2.pdf"); // intercept delete file request cy.intercept("/api/v2/boreholefile/detachFile?boreholeId=**&boreholeFileId=**").as("delete-file"); // delete attachments - cy.get("tbody").children().first().get("td button").children().first().click(); + cy.get('[data-cy="attachments-detach-button"]').children().first().click(); cy.wait(["@delete-file"]); cy.wait(["@getAllAttachments"]); - cy.get("tbody").children().first().get("td button").children().first().click(); + cy.get("tbody").children().should("have.length", 2); + cy.get('[data-cy="attachments-detach-button"]').children().first().click(); + cy.wait(["@delete-file"]); + cy.wait(["@getAllAttachments"]); + cy.get("tbody").children().should("have.length", 1); + cy.get('[data-cy="attachments-detach-button"]').children().first().click(); cy.wait(["@delete-file"]); cy.wait(["@getAllAttachments"]); cy.get("tbody").children().should("have.length", 0); diff --git a/src/client/src/pages/detail/attachments/table/filesTableComponent.jsx b/src/client/src/pages/detail/attachments/table/filesTableComponent.jsx index 539182184..5a56ea33c 100644 --- a/src/client/src/pages/detail/attachments/table/filesTableComponent.jsx +++ b/src/client/src/pages/detail/attachments/table/filesTableComponent.jsx @@ -89,6 +89,7 @@ const FilesTableComponent = props => { {props.unlocked === true ? ( { e.stopPropagation(); props.detachFile(props.id, boreholeFile.fileId); diff --git a/tests/api/Controllers/BoreholeFileControllerTest.cs b/tests/api/Controllers/BoreholeFileControllerTest.cs index e532d323f..75f432c63 100644 --- a/tests/api/Controllers/BoreholeFileControllerTest.cs +++ b/tests/api/Controllers/BoreholeFileControllerTest.cs @@ -1,10 +1,7 @@ using Amazon.S3; -using Amazon.S3.Model; -using Azure; using BDMS.Authentication; using BDMS.Models; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -96,7 +93,6 @@ public async Task UploadAndDownload() Assert.AreEqual(content, contentResult); // Get file - Assert.AreNotEqual(null, file.Hash); Assert.AreEqual(DateTime.UtcNow.Date, file.Created?.Date); Assert.AreEqual(adminUser.SubjectId, file.CreatedBy.SubjectId); Assert.AreEqual(adminUser.Id, file.CreatedById); @@ -167,7 +163,7 @@ public async Task GetAllOfBorehole() } [TestMethod] - public async Task DetachFromBoreholeWithFileUsedByOtherBoreholeShouldDetachFile() + public async Task DetachFromBoreholeWithFileUsedByMultipleBoreholeShouldDetachAndDeleteFile() { // Get borehole Ids var firstBoreholeId = context.Boreholes.First().Id; @@ -182,24 +178,25 @@ public async Task DetachFromBoreholeWithFileUsedByOtherBoreholeShouldDetachFile( // Create file to upload var pdfFormFile = GetFormFileByContent(Guid.NewGuid().ToString(), "file_1.pdf"); - // Upload file for boreholes + // Upload file for both boreholes await controller.Upload(pdfFormFile, firstBoreholeId); await controller.Upload(pdfFormFile, secondBoreholeId); // Check counts after upload - Assert.AreEqual(filesCountBeforeUpload + 1, context.Files.Count()); + Assert.AreEqual(filesCountBeforeUpload + 2, context.Files.Count()); Assert.AreEqual(boreholeFilesCountBeforeUpload + 2, context.BoreholeFiles.Count()); Assert.AreEqual(firstBoreholeBoreholeFilesBeforeUpload + 1, context.BoreholeFiles.Where(bf => bf.BoreholeId == firstBoreholeId).Count()); Assert.AreEqual(secondBoreholeBoreholeFilesBeforeUpload + 1, context.BoreholeFiles.Where(bf => bf.BoreholeId == secondBoreholeId).Count()); - // Get latest file in db - var latestFileInDb = context.Files.OrderBy(f => f.Id).Last(); + // Get the added files + var firstBoreholeAddedFile = context.BoreholeFiles.Where(bf => bf.BoreholeId == firstBoreholeId).OrderBy(bf => bf.FileId).Last().File; + var secondBoreholeAddedFile = context.BoreholeFiles.Where(bf => bf.BoreholeId == secondBoreholeId).OrderBy(bf => bf.FileId).Last().File; // Clear context to ensure file has no info about its boreholeFiles context.ChangeTracker.Clear(); // Detach borehole file from first borehole - await controller.DetachFromBorehole(firstBoreholeId, latestFileInDb.BoreholeFiles.First(bf => bf.BoreholeId == firstBoreholeId).FileId); + await controller.DetachFromBorehole(firstBoreholeId, firstBoreholeAddedFile.BoreholeFiles.First(bf => bf.BoreholeId == firstBoreholeId).FileId); // Check counts after detach Assert.AreEqual(filesCountBeforeUpload + 1, context.Files.Count()); @@ -207,18 +204,19 @@ public async Task DetachFromBoreholeWithFileUsedByOtherBoreholeShouldDetachFile( Assert.AreEqual(firstBoreholeBoreholeFilesBeforeUpload, context.BoreholeFiles.Where(bf => bf.BoreholeId == firstBoreholeId).Count()); Assert.AreEqual(secondBoreholeBoreholeFilesBeforeUpload + 1, context.BoreholeFiles.Where(bf => bf.BoreholeId == secondBoreholeId).Count()); - // Ensure file exists - await boreholeFileCloudService.GetObject(latestFileInDb.NameUuid!); + // Ensure the file got deleted for the first borehole + var exception = await Assert.ThrowsExceptionAsync(() => boreholeFileCloudService.GetObject(firstBoreholeAddedFile.NameUuid!)); + Assert.AreEqual("The specified key does not exist.", exception.Message); + + // Ensure the file still exists for the second borehole + await boreholeFileCloudService.GetObject(secondBoreholeAddedFile.NameUuid!); } [TestMethod] public async Task DetachFromBoreholeWithFileNotUsedByOtherBoreholeShouldDetachAndDeleteFile() { - var fileName = $"{Guid.NewGuid()}.pdf"; - // Get borehole Ids var firstBoreholeId = context.Boreholes.First().Id; - var secondBoreholeId = context.Boreholes.Skip(1).First().Id; // Get counts before upload var filesCountBeforeUpload = context.Files.Count(); @@ -260,7 +258,7 @@ public async Task UpdateWithValidBoreholeFile() var borehole = new Borehole(); context.Boreholes.Add(borehole); - var file = new Models.File() { Name = $"{Guid.NewGuid}.pdf", NameUuid = $"{Guid.NewGuid}.pdf", Hash = Guid.NewGuid().ToString(), Type = "pdf" }; + var file = new Models.File() { Name = $"{Guid.NewGuid}.pdf", NameUuid = $"{Guid.NewGuid}.pdf", Type = "pdf" }; context.Files.Add(file); await context.SaveChangesAsync().ConfigureAwait(false); @@ -303,15 +301,15 @@ public async Task UploadWithMissingBoreholeFileId() } [TestMethod] - public async Task UploadWithFileAlreadyAttachedShouldThrowError() + public async Task CanUploadIdenticalFileMultipleTimes() { - var fileName = $"{Guid.NewGuid()}.pdf"; var minBoreholeId = context.Boreholes.Min(b => b.Id); - var pdfFormFile = GetFormFileByContent(Guid.NewGuid().ToString(), fileName); + var fileContent = "ANT-VII, REDASSOCIATION\r\nMONKEYBONES"; - await controller.Upload(pdfFormFile, minBoreholeId); - - await AssertIsBadRequestResponse(() => controller.Upload(pdfFormFile, minBoreholeId)); + // Upload same content using the same and different file names + await AssertIsOkResponse(() => controller.Upload(GetFormFileByContent(fileContent, "IRATEWATCH.pdf"), minBoreholeId)); + await AssertIsOkResponse(() => controller.Upload(GetFormFileByContent(fileContent, "IRATEWATCH.pdf"), minBoreholeId)); + await AssertIsOkResponse(() => controller.Upload(GetFormFileByContent(fileContent, "PAINTEDSHADOW.png"), minBoreholeId)); } [TestMethod] @@ -459,9 +457,9 @@ public async Task GetDataExtractionImage() await boreholeFileCloudService.DeleteObject($"dataextraction/{fileUuid}-1.png"); } - private async Task AssertIsBadRequestResponse(Func> action) - { - var result = await action(); - ActionResultAssert.IsBadRequest(result); - } + private static async Task AssertIsBadRequestResponse(Func> func) => + ActionResultAssert.IsBadRequest(await func()); + + private static async Task AssertIsOkResponse(Func> func) => + ActionResultAssert.IsOk(await func()); } From b1494956e6a75f6bfc485225738135ce021a8534 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 5 Dec 2024 14:18:01 +0100 Subject: [PATCH 3/7] Do not copy borehole attachments This feature will be re-added in the future. --- src/api/Controllers/BoreholeController.cs | 6 ++---- tests/api/Controllers/BoreholeControllerTest.cs | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/Controllers/BoreholeController.cs b/src/api/Controllers/BoreholeController.cs index 135a71cc2..8875ffeb9 100644 --- a/src/api/Controllers/BoreholeController.cs +++ b/src/api/Controllers/BoreholeController.cs @@ -296,10 +296,8 @@ await Context.Entry(hydrotest) } } - foreach (var boreholeFile in borehole.BoreholeFiles) - { - boreholeFile.BoreholeId = 0; - } + // Do not copy borehole attachments + borehole.BoreholeFiles.Clear(); foreach (var boreholeGeometry in borehole.BoreholeGeometry) { diff --git a/tests/api/Controllers/BoreholeControllerTest.cs b/tests/api/Controllers/BoreholeControllerTest.cs index 09983581a..c05f0b975 100644 --- a/tests/api/Controllers/BoreholeControllerTest.cs +++ b/tests/api/Controllers/BoreholeControllerTest.cs @@ -374,10 +374,10 @@ public async Task Copy() Assert.AreNotEqual(originalStratigraphy.LithostratigraphyLayers.First().Id, copiedstratigraphy.LithostratigraphyLayers.First().Id); Assert.AreEqual(originalStratigraphy.LithostratigraphyLayers.OrderBy(l => l.Id).First().LithostratigraphyId, copiedstratigraphy.LithostratigraphyLayers.OrderBy(l => l.Id).First().LithostratigraphyId); + // Borehole attachments are not copied Assert.AreNotSame(originalBorehole.BoreholeFiles, copiedBorehole.BoreholeFiles); - Assert.AreNotEqual(originalBorehole.BoreholeFiles.First().BoreholeId, copiedBorehole.BoreholeFiles.First().BoreholeId); - Assert.AreEqual(originalBorehole.BoreholeFiles.First().FileId, copiedBorehole.BoreholeFiles.First().FileId); - Assert.AreEqual(originalBorehole.BoreholeFiles.First().Description, copiedBorehole.BoreholeFiles.First().Description); + Assert.AreNotEqual(0, originalBorehole.BoreholeFiles.Count); + Assert.AreEqual(0, copiedBorehole.BoreholeFiles.Count); Assert.AreNotSame(originalStratigraphy.Layers.First().LayerColorCodes, copiedstratigraphy.Layers.First().LayerColorCodes); Assert.AreEqual(originalStratigraphy.Layers.First().LayerColorCodes.Count, copiedstratigraphy.Layers.First().LayerColorCodes.Count); From 122edfffae639fa784d49b2687dbf6c1cfbd4a42 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 22 Nov 2024 15:46:52 +0100 Subject: [PATCH 4/7] Add release notes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5feaf2461..8b1048f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ - The `alternate_name` is now displayed in the borehole detail header and the map markers. - From depth and to depth are no longer displayed in groundwater level measurements. - Updated the layout of the borehole general tab. +- Removed deduplication check when adding and detaching attachments. +- When copying a borehole, attachments won't be copied. ### Fixed From add6130fb97d9ead8753b238fcd7d44e2ae86ff0 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 5 Dec 2024 15:38:17 +0100 Subject: [PATCH 5/7] Improve readability --- src/api/BoreholeFileCloudService.cs | 20 +++++++++---------- src/api/Controllers/BoreholeFileController.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/api/BoreholeFileCloudService.cs b/src/api/BoreholeFileCloudService.cs index 26924a6f8..5cd5585e9 100644 --- a/src/api/BoreholeFileCloudService.cs +++ b/src/api/BoreholeFileCloudService.cs @@ -31,9 +31,9 @@ public BoreholeFileCloudService(BdmsContext context, IConfiguration configuratio /// /// Uploads a file to the cloud storage and links it to the borehole. /// - /// The file to upload and link to the . - /// The to link the uploaded to. - public async Task UploadFileAndLinkToBorehole(IFormFile file, int boreholeId) + /// The file to upload and link to the . + /// The to link the uploaded to. + public async Task UploadFileAndLinkToBorehole(IFormFile formFile, int boreholeId) { // Use transaction to ensure data is only stored to db if the file upload was sucessful. Only create a transaction if there is not already one from the calling method. using var transaction = context.Database.CurrentTransaction == null ? await context.Database.BeginTransactionAsync().ConfigureAwait(false) : null; @@ -49,22 +49,22 @@ public async Task UploadFileAndLinkToBorehole(IFormFile file, int if (user == null || subjectId == null) throw new InvalidOperationException($"No user with subject_id <{subjectId}> found."); // Register the new file in the boreholes database. - var fileExtension = Path.GetExtension(file.FileName); + var fileExtension = Path.GetExtension(formFile.FileName); var fileNameGuid = $"{Guid.NewGuid()}{fileExtension}"; - var bdmsFile = new Models.File { Name = file.FileName, NameUuid = fileNameGuid, Type = file.ContentType }; + var file = new Models.File { Name = formFile.FileName, NameUuid = fileNameGuid, Type = formFile.ContentType }; - await context.Files.AddAsync(bdmsFile).ConfigureAwait(false); + await context.Files.AddAsync(file).ConfigureAwait(false); await context.UpdateChangeInformationAndSaveChangesAsync(httpContextAccessor.HttpContext!).ConfigureAwait(false); - var fileId = bdmsFile.Id; + var fileId = file.Id; // Upload the file to the cloud storage. - await UploadObject(file, fileNameGuid).ConfigureAwait(false); + await UploadObject(formFile, fileNameGuid).ConfigureAwait(false); // If file is already linked to the borehole, throw an exception. if (await context.BoreholeFiles.AnyAsync(bf => bf.BoreholeId == boreholeId && bf.FileId == fileId).ConfigureAwait(false)) - throw new InvalidOperationException($"File <{file.FileName}> is already attached to borehole with Id <{boreholeId}>."); + throw new InvalidOperationException($"File <{formFile.FileName}> is already attached to borehole with Id <{boreholeId}>."); // Link file to the borehole. var boreholeFile = new BoreholeFile { FileId = fileId, BoreholeId = boreholeId, UserId = user.Id, Attached = DateTime.UtcNow }; @@ -77,7 +77,7 @@ public async Task UploadFileAndLinkToBorehole(IFormFile file, int } catch (Exception ex) { - logger.LogError(ex, "Error attaching file <{FileName}> to borehole with Id <{BoreholeId}>.", file.FileName, boreholeId); + logger.LogError(ex, "Error attaching file <{FileName}> to borehole with Id <{BoreholeId}>.", formFile.FileName, boreholeId); throw; } } diff --git a/src/api/Controllers/BoreholeFileController.cs b/src/api/Controllers/BoreholeFileController.cs index 3ad572355..6568c36b3 100644 --- a/src/api/Controllers/BoreholeFileController.cs +++ b/src/api/Controllers/BoreholeFileController.cs @@ -212,7 +212,7 @@ public async Task DetachFromBorehole([Required, Range(1, int.MaxV // Get the file and its borehole files from the database. var boreholeFile = await context.BoreholeFiles.Include(f => f.File).SingleOrDefaultAsync(f => f.FileId == boreholeFileId).ConfigureAwait(false); - if (boreholeFile == null) return NotFound("Borehole file for the provided boreholeFileId not found."); + if (boreholeFile == null) return NotFound($"Borehole file for the provided {nameof(boreholeFileId)} not found."); var fileId = boreholeFile.File.Id; From 7286e42c6b28e5e2fcb3eebacf9b0a0b9ec40868 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Thu, 5 Dec 2024 16:28:25 +0100 Subject: [PATCH 6/7] Update db context model snapshot --- src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs | 5 ----- .../20241128080655_RemoveGroundwaterDepthEntries.Designer.cs | 5 ----- src/api/Migrations/BdmsContextModelSnapshot.cs | 5 ----- 3 files changed, 15 deletions(-) diff --git a/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs b/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs index 222d6a486..588b55ead 100644 --- a/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs +++ b/src/api/Migrations/20241122080247_RemoveFileHash.Designer.cs @@ -901,11 +901,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("integer") .HasColumnName("id_usr_fk"); - b.Property("Hash") - .IsRequired() - .HasColumnType("text") - .HasColumnName("hash_fil"); - b.Property("Name") .IsRequired() .HasColumnType("text") diff --git a/src/api/Migrations/20241128080655_RemoveGroundwaterDepthEntries.Designer.cs b/src/api/Migrations/20241128080655_RemoveGroundwaterDepthEntries.Designer.cs index df76964e0..2db47aea8 100644 --- a/src/api/Migrations/20241128080655_RemoveGroundwaterDepthEntries.Designer.cs +++ b/src/api/Migrations/20241128080655_RemoveGroundwaterDepthEntries.Designer.cs @@ -901,11 +901,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("integer") .HasColumnName("id_usr_fk"); - b.Property("Hash") - .IsRequired() - .HasColumnType("text") - .HasColumnName("hash_fil"); - b.Property("Name") .IsRequired() .HasColumnType("text") diff --git a/src/api/Migrations/BdmsContextModelSnapshot.cs b/src/api/Migrations/BdmsContextModelSnapshot.cs index 920e88271..b0273fed4 100644 --- a/src/api/Migrations/BdmsContextModelSnapshot.cs +++ b/src/api/Migrations/BdmsContextModelSnapshot.cs @@ -898,11 +898,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("integer") .HasColumnName("id_usr_fk"); - b.Property("Hash") - .IsRequired() - .HasColumnType("text") - .HasColumnName("hash_fil"); - b.Property("Name") .IsRequired() .HasColumnType("text") From 23f5431c2bf71ab2e0a0bc60027c1445a5b0fb76 Mon Sep 17 00:00:00 2001 From: Oliver Gut Date: Fri, 6 Dec 2024 07:47:05 +0100 Subject: [PATCH 7/7] Improve readability --- .../cypress/e2e/detailPage/attachments.cy.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/client/cypress/e2e/detailPage/attachments.cy.js b/src/client/cypress/e2e/detailPage/attachments.cy.js index 392b4d515..b5a7ce310 100644 --- a/src/client/cypress/e2e/detailPage/attachments.cy.js +++ b/src/client/cypress/e2e/detailPage/attachments.cy.js @@ -36,8 +36,7 @@ describe("Tests for 'Attachments' edit page.", () => { // // upload file cy.get('[data-cy="attachments-upload-button"]').should("be.visible").click(); - cy.wait(["@upload-files"]); - cy.wait(["@getAllAttachments"]); + cy.wait(["@upload-files", "@getAllAttachments"]); // check list of attachments cy.get("tbody").children().should("have.length", 1); @@ -56,8 +55,7 @@ describe("Tests for 'Attachments' edit page.", () => { // upload and verify file IRATETRINITY.pdf cy.get('[data-cy="attachments-upload-button"]').should("be.visible").click(); - cy.wait(["@upload-files"]); - cy.wait(["@getAllAttachments"]); + cy.wait(["@upload-files", "@getAllAttachments"]); cy.get("tbody").children().should("have.length", 2); cy.get("tbody").children().contains("td", "text/plain"); cy.get("tbody").children().contains("td", "application/pdf"); @@ -72,8 +70,7 @@ describe("Tests for 'Attachments' edit page.", () => { { force: true }, ); cy.get('[data-cy="attachments-upload-button"]').should("be.visible").click(); - cy.wait(["@upload-files"]); - cy.wait(["@getAllAttachments"]); + cy.wait(["@upload-files", "@getAllAttachments"]); cy.get("tbody").children().should("have.length", 3); cy.get("tbody").children().contains("td", "text/plain"); cy.get("tbody").children().contains("td", "application/pdf"); @@ -96,16 +93,13 @@ describe("Tests for 'Attachments' edit page.", () => { // delete attachments cy.get('[data-cy="attachments-detach-button"]').children().first().click(); - cy.wait(["@delete-file"]); - cy.wait(["@getAllAttachments"]); + cy.wait(["@delete-file", "@getAllAttachments"]); cy.get("tbody").children().should("have.length", 2); cy.get('[data-cy="attachments-detach-button"]').children().first().click(); - cy.wait(["@delete-file"]); - cy.wait(["@getAllAttachments"]); + cy.wait(["@delete-file", "@getAllAttachments"]); cy.get("tbody").children().should("have.length", 1); cy.get('[data-cy="attachments-detach-button"]').children().first().click(); - cy.wait(["@delete-file"]); - cy.wait(["@getAllAttachments"]); + cy.wait(["@delete-file", "@getAllAttachments"]); cy.get("tbody").children().should("have.length", 0); // stop editing