From 64fdb6318cde987595a4fceb844d666b81d73565 Mon Sep 17 00:00:00 2001
From: Hamid Reza Mohammadi
Date: Fri, 11 Sep 2026 16:45:47 +0330
Subject: [PATCH] semantic search
---
.../_SemanticSearchWidgetPartial.cshtml | 30 +-
.../Controllers/SemanticSearchController.cs | 16 +
RMuseum/DbContext/RMuseumDbContext.cs | 7 +
...4747_AddSemanticSearchQueryLog.Designer.cs | 6174 +++++++++++++++++
...0260911114747_AddSemanticSearchQueryLog.cs | 46 +
.../RMuseumDbContextModelSnapshot.cs | 49 +
.../SemanticSearch/SemanticSearchDtos.cs | 23 +-
.../SemanticSearch/SemanticSearchQueryLog.cs | 33 +
RMuseum/RMuseum.xml | 121 +-
.../Implementation/SemanticSearchService.cs | 61 +
10 files changed, 6548 insertions(+), 12 deletions(-)
create mode 100644 RMuseum/Migrations/20260911114747_AddSemanticSearchQueryLog.Designer.cs
create mode 100644 RMuseum/Migrations/20260911114747_AddSemanticSearchQueryLog.cs
create mode 100644 RMuseum/Models/Ganjoor/SemanticSearch/SemanticSearchQueryLog.cs
diff --git a/GanjooRazor/Pages/Shared/_SemanticSearchWidgetPartial.cshtml b/GanjooRazor/Pages/Shared/_SemanticSearchWidgetPartial.cshtml
index a4c1cc12..f41f9b93 100644
--- a/GanjooRazor/Pages/Shared/_SemanticSearchWidgetPartial.cshtml
+++ b/GanjooRazor/Pages/Shared/_SemanticSearchWidgetPartial.cshtml
@@ -311,9 +311,12 @@
' (جستجوی سراسری)
';
}
- response.results.forEach(function (item) {
+ response.results.forEach(function (item, index) {
+ var rank = index + 1;
html += '';
});
@@ -321,6 +324,29 @@
resultsEl.innerHTML = html;
}
+ // Fire-and-forget - does not preventDefault or delay navigation in any way, just queues the
+ // click report in the background while the browser proceeds to the poem page normally.
+ // sendBeacon is specifically designed for "report something right as the page is about to
+ // unload" - a plain fetch can get cancelled mid-flight by the navigation; sendBeacon (or a
+ // keepalive fetch as fallback for older browsers) doesn't have that problem.
+ function ssWidgetReportClick(logId, poemId, rank) {
+ if (!logId) return; // the search itself failed to log - nothing to correlate a click to
+
+ var payload = JSON.stringify({ logId: logId, poemId: poemId, rank: rank });
+ var url = ssWidgetApiRoot + "/api/ganjoor/search/semantic/click";
+
+ if (navigator.sendBeacon) {
+ navigator.sendBeacon(url, new Blob([payload], { type: "application/json" }));
+ } else {
+ fetch(url, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: payload,
+ keepalive: true
+ }).catch(function () { });
+ }
+ }
+
// Same hemistich-pairing logic as mini-ganjoor's renderVerses (https://ganjoor.github.io/mini/)
// and SemanticSearch.cshtml - reused as-is rather than reinvented a third time.
function ssWidgetRenderVerses(verses) {
diff --git a/RMuseum/Controllers/SemanticSearchController.cs b/RMuseum/Controllers/SemanticSearchController.cs
index a29f3ff0..44a54a09 100644
--- a/RMuseum/Controllers/SemanticSearchController.cs
+++ b/RMuseum/Controllers/SemanticSearchController.cs
@@ -61,5 +61,21 @@ namespace RMuseum.Controllers
return BadRequest(exp.ToString());
}
}
+
+ ///
+ /// Fire-and-forget click reporting — called via navigator.sendBeacon (or a keepalive
+ /// fetch as fallback) right as a result link is clicked, so it can complete even as the
+ /// browser navigates away. ReportClickAsync itself is fully best-effort (never throws in
+ /// a way that matters here), so this always returns 200 regardless of whether the
+ /// underlying write actually succeeded — the caller isn't listening for the response
+ /// either way.
+ ///
+ [HttpPost("search/semantic/click")]
+ [ProducesResponseType((int)HttpStatusCode.OK)]
+ public async Task SemanticSearchClick([FromBody] SemanticSearchClickDto click)
+ {
+ await _semanticSearchService.ReportClickAsync(click);
+ return Ok();
+ }
}
}
diff --git a/RMuseum/DbContext/RMuseumDbContext.cs b/RMuseum/DbContext/RMuseumDbContext.cs
index a59d73cd..a16267a9 100644
--- a/RMuseum/DbContext/RMuseumDbContext.cs
+++ b/RMuseum/DbContext/RMuseumDbContext.cs
@@ -10,6 +10,7 @@ using RSecurityBackend.DbContext;
using RSecurityBackend.Models.Auth.Db;
using System;
using RMuseum.Models.Ganjoor;
+using RMuseum.Models.Ganjoor.SemanticSearch;
using RMuseum.Models.MusicCatalogue;
using RMuseum.Models.Accounting;
using Microsoft.Extensions.Configuration;
@@ -353,6 +354,12 @@ namespace RMuseum.DbContext
///
public DbSet GanjoorPoems { get; set; }
+ ///
+ /// Semantic search query log — see SemanticSearchQueryLog for what is (and deliberately
+ /// isn't) recorded
+ ///
+ public DbSet SemanticSearchQueryLogs { get; set; }
+
///
/// Ganjoor Verses
///
diff --git a/RMuseum/Migrations/20260911114747_AddSemanticSearchQueryLog.Designer.cs b/RMuseum/Migrations/20260911114747_AddSemanticSearchQueryLog.Designer.cs
new file mode 100644
index 00000000..dffdb94d
--- /dev/null
+++ b/RMuseum/Migrations/20260911114747_AddSemanticSearchQueryLog.Designer.cs
@@ -0,0 +1,6174 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using RMuseum.DbContext;
+
+#nullable disable
+
+namespace RMuseum.Migrations
+{
+ [DbContext(typeof(RMuseumDbContext))]
+ [Migration("20260911114747_AddSemanticSearchQueryLog")]
+ partial class AddSemanticSearchQueryLog
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.11")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ClaimValue")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RoleId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("LoginProvider")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Accounting.DonationExpenditure", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Amount")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("GanjoorDonationId")
+ .HasColumnType("int");
+
+ b.Property("GanjoorExpenseId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GanjoorDonationId");
+
+ b.HasIndex("GanjoorExpenseId");
+
+ b.ToTable("DonationExpenditure");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Accounting.GanjoorDonation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Amount")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("AmountString")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DateString")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DonorLink")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DonorName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ExpenditureDesc")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ImportedRecord")
+ .HasColumnType("bit");
+
+ b.Property("RecordDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Remaining")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Unit")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("GanjoorDonations");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Accounting.GanjoorExpense", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Amount")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ExpenseDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Unit")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("GanjoorExpenses");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Artifact.RArtifactItemRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CoverImageIndex")
+ .HasColumnType("int");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DescriptionInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FriendlyUrl")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NameInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("RArtifactMasterRecordId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RArtifactMasterRecordId", "FriendlyUrl")
+ .IsUnique()
+ .HasFilter("[FriendlyUrl] IS NOT NULL");
+
+ b.HasIndex("RArtifactMasterRecordId", "Order")
+ .IsUnique();
+
+ b.ToTable("Items");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Artifact.RArtifactMasterRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CoverImageId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CoverItemIndex")
+ .HasColumnType("int");
+
+ b.Property("DateTime")
+ .HasColumnType("datetime2");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DescriptionInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FriendlyUrl")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("ItemCount")
+ .HasColumnType("int");
+
+ b.Property("LastModified")
+ .HasColumnType("datetime2");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NameInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CoverImageId");
+
+ b.HasIndex("FriendlyUrl")
+ .IsUnique()
+ .HasFilter("[FriendlyUrl] IS NOT NULL");
+
+ b.HasIndex("LastModified");
+
+ b.HasIndex("CoverItemIndex", "Status");
+
+ b.ToTable("Artifacts");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Artifact.RTag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("FriendlyUrl")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("GlobalValue")
+ .HasColumnType("bit");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NameInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("PluralName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PluralNameInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("TagType")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FriendlyUrl");
+
+ b.ToTable("Tags");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Artifact.RTagValue", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("BookId")
+ .HasColumnType("int");
+
+ b.Property("FriendlyUrl")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("PDFBookId")
+ .HasColumnType("int");
+
+ b.Property("PDFPageId")
+ .HasColumnType("int");
+
+ b.Property("RArtifactItemRecordId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RArtifactMasterRecordId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RTagId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("Value")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ValueInEnglish")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ValueSupplement")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BookId");
+
+ b.HasIndex("FriendlyUrl");
+
+ b.HasIndex("PDFBookId");
+
+ b.HasIndex("PDFPageId");
+
+ b.HasIndex("RArtifactItemRecordId");
+
+ b.HasIndex("RArtifactMasterRecordId");
+
+ b.HasIndex("RTagId");
+
+ b.ToTable("TagValues");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Bookmark.RUserBookmark", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("DateTime")
+ .HasColumnType("datetime2");
+
+ b.Property("Note")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RAppUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RArtifactItemRecordId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RArtifactMasterRecordId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("RBookmarkType")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RAppUserId");
+
+ b.HasIndex("RArtifactItemRecordId");
+
+ b.HasIndex("RArtifactMasterRecordId");
+
+ b.ToTable("UserBookmarks");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.ExternalFTPUpload.QueuedFTPUpload", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("DeleteFileAfterUpload")
+ .HasColumnType("bit");
+
+ b.Property("Error")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("LocalFilePath")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ProcessDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Processing")
+ .HasColumnType("bit");
+
+ b.Property("QueueDate")
+ .HasColumnType("datetime2");
+
+ b.Property("RemoteFilePath")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("QueuedFTPUploads");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.FAQ.FAQCategory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CatOrder")
+ .HasColumnType("int");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Published")
+ .HasColumnType("bit");
+
+ b.Property("Title")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("FAQCategories");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.FAQ.FAQItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AnswerExcerpt")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CategoryId")
+ .HasColumnType("int");
+
+ b.Property("ContentForSearch")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FullAnswer")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HashTag1")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HashTag2")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HashTag3")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HashTag4")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ItemOrderInCategory")
+ .HasColumnType("int");
+
+ b.Property("Pinned")
+ .HasColumnType("bit");
+
+ b.Property("PinnedItemOrder")
+ .HasColumnType("int");
+
+ b.Property("Published")
+ .HasColumnType("bit");
+
+ b.Property("Question")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CategoryId");
+
+ b.ToTable("FAQItems");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.CategoryWordCount", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CatId")
+ .HasColumnType("int");
+
+ b.Property("Count")
+ .HasColumnType("int");
+
+ b.Property("RowNmbrInCat")
+ .HasColumnType("int");
+
+ b.Property("Word")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CatId", "Word")
+ .IsUnique()
+ .HasFilter("[Word] IS NOT NULL");
+
+ b.ToTable("CategoryWordCounts");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.CategoryWordCountSummary", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("CatId")
+ .HasColumnType("int");
+
+ b.Property("TotalWordCount")
+ .HasColumnType("int");
+
+ b.Property("UniqueWordCount")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CatId")
+ .IsUnique();
+
+ b.ToTable("CategoryWordCountSummaries");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.DigitalSource", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CoupletsCount")
+ .HasColumnType("int");
+
+ b.Property("FullName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ShortName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SourceType")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UrlSlug")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.ToTable("DigitalSources");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.DiscoverQuotedQueueItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("PoemId")
+ .HasColumnType("int");
+
+ b.Property("PoetId")
+ .HasColumnType("int");
+
+ b.Property("RelatedPoemId")
+ .HasColumnType("int");
+
+ b.Property("RelatedPoetId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("DiscoverQuotedQueueItems");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCachedRelatedPoem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("FullTitle")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FullUrl")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("HtmlExcerpt")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PoemId")
+ .HasColumnType("int");
+
+ b.Property("PoetId")
+ .HasColumnType("int");
+
+ b.Property("PoetImageUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PoetMorePoemsLikeThisCount")
+ .HasColumnType("int");
+
+ b.Property("PoetName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RelationOrder")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FullUrl");
+
+ b.HasIndex("PoemId");
+
+ b.ToTable("GanjoorCachedRelatedPoems");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCachedRelatedSection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("FullTitle")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FullUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HtmlExcerpt")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PoemId")
+ .HasColumnType("int");
+
+ b.Property("PoetId")
+ .HasColumnType("int");
+
+ b.Property("PoetImageUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PoetMorePoemsLikeThisCount")
+ .HasColumnType("int");
+
+ b.Property("PoetName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RelationOrder")
+ .HasColumnType("int");
+
+ b.Property("SectionIndex")
+ .HasColumnType("int");
+
+ b.Property("TargetPoemId")
+ .HasColumnType("int");
+
+ b.Property("TargetSectionIndex")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PoemId", "SectionIndex");
+
+ b.ToTable("GanjoorCachedRelatedSections");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCat", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("int");
+
+ b.Property("BookName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CatType")
+ .HasColumnType("int");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DescriptionHtml")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FullUrl")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("MapName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("MixedModeOrder")
+ .HasColumnType("int");
+
+ b.Property("ParentId")
+ .HasColumnType("int");
+
+ b.Property("PoetId")
+ .HasColumnType("int");
+
+ b.Property("Published")
+ .HasColumnType("bit");
+
+ b.Property("RImageId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("SumUpSubsGeoLocations")
+ .HasColumnType("bit");
+
+ b.Property("TableOfContentsStyle")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UrlSlug")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("FullUrl");
+
+ b.HasIndex("PoetId");
+
+ b.HasIndex("RImageId");
+
+ b.HasIndex("ParentId", "PoetId");
+
+ SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex("ParentId", "PoetId"), new[] { "Id" });
+
+ b.ToTable("GanjoorCategories");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCatCorrection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AffectedTheCat")
+ .HasColumnType("bit");
+
+ b.Property("ApplicationOrder")
+ .HasColumnType("int");
+
+ b.Property("CatId")
+ .HasColumnType("int");
+
+ b.Property("Date")
+ .HasColumnType("datetime2");
+
+ b.Property("Description")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("DescriptionHtml")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("HideMyName")
+ .HasColumnType("bit");
+
+ b.Property("Note")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OriginalDescription")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("OriginalDescriptionHtml")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PageId")
+ .HasColumnType("int");
+
+ b.Property("Result")
+ .HasColumnType("int");
+
+ b.Property("ReviewDate")
+ .HasColumnType("datetime2");
+
+ b.Property("ReviewNote")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Reviewed")
+ .HasColumnType("bit");
+
+ b.Property("ReviewerUserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CatId");
+
+ b.HasIndex("ReviewerUserId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("GanjoorCatCorrections");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCentury", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("EndYear")
+ .HasColumnType("int");
+
+ b.Property("HalfCenturyOrder")
+ .HasColumnType("int");
+
+ b.Property("Name")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ShowInTimeLine")
+ .HasColumnType("bit");
+
+ b.Property("StartYear")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("GanjoorCenturies");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCenturyPoet", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("GanjoorCenturyId")
+ .HasColumnType("int");
+
+ b.Property("PoetId")
+ .HasColumnType("int");
+
+ b.Property("PoetOrder")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GanjoorCenturyId");
+
+ b.HasIndex("PoetId");
+
+ b.ToTable("GanjoorCenturyPoet");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorComment", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AuthorEmail")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AuthorIpAddress")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AuthorName")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AuthorUrl")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CommentDate")
+ .HasColumnType("datetime2");
+
+ b.Property("CoupletIndex")
+ .HasColumnType("int");
+
+ b.Property("DislikeCount")
+ .HasColumnType("int");
+
+ b.Property("HtmlComment")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InReplyToId")
+ .HasColumnType("int");
+
+ b.Property("LikeCount")
+ .HasColumnType("int");
+
+ b.Property("PoemId")
+ .HasColumnType("int");
+
+ b.Property("SortKey")
+ .HasColumnType("int");
+
+ b.Property("Status")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("Verse12d")
+ .HasColumnType("int");
+
+ b.Property("Verse1Id")
+ .HasColumnType("int");
+
+ b.Property("Verse2Id")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CommentDate");
+
+ b.HasIndex("InReplyToId");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("Verse1Id");
+
+ b.HasIndex("Verse2Id");
+
+ b.HasIndex("PoemId", "CommentDate");
+
+ b.HasIndex("PoemId", "SortKey");
+
+ b.ToTable("GanjoorComments");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCommentAbuseReport", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("GanjoorCommentId")
+ .HasColumnType("int");
+
+ b.Property("ReasonCode")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReasonText")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReportedById")
+ .HasColumnType("uniqueidentifier");
+
+ b.HasKey("Id");
+
+ b.HasIndex("GanjoorCommentId");
+
+ b.HasIndex("ReportedById");
+
+ b.ToTable("GanjoorReportedComments");
+ });
+
+ modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCommentReaction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property