From bb68c074b0e80ad56c5130641208360605ce6c34 Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Tue, 29 Sep 2026 19:32:57 +0330 Subject: [PATCH] people tags #387 --- .../Admin/Pages/ReviewPersonEdits.cshtml | 132 +- .../Pages/ReviewPersonRelationEdits.cshtml | 199 + .../Pages/ReviewPersonRelationEdits.cshtml.cs | 155 + GanjooRazor/Pages/Person.cshtml | 18 + GanjooRazor/Pages/Person.cshtml.cs | 7 + GanjooRazor/Pages/Shared/_AdminLayout.cshtml | 2 + .../Pages/SuggestNewPersonRelation.cshtml | 103 + .../Pages/SuggestNewPersonRelation.cshtml.cs | 185 + GanjooRazor/Pages/SuggestPersonEdit.cshtml | 7 + .../Pages/SuggestPersonRelationEdit.cshtml | 105 + .../Pages/SuggestPersonRelationEdit.cshtml.cs | 133 + .../GanjoorRelatedPersonController.cs | 94 + RMuseum/DbContext/RMuseumDbContext.cs | 29 + ...orPersonRelationEditSuggestion.Designer.cs | 6595 +++++++++++++++++ ..._AddGanjoorPersonRelationEditSuggestion.cs | 114 + .../RMuseumDbContextModelSnapshot.cs | 111 + .../Ganjoor/GanjoorPersonEditSuggestion.cs | 10 + .../GanjoorPersonRelationEditSuggestion.cs | 128 + .../Ganjoor/PersonRelationSuggestionAction.cs | 23 + .../GanjoorPersonRelationsViewModel.cs | 6 + RMuseum/RMuseum.xml | 319 +- .../Services/IGanjoorRelatedPersonService.cs | 42 + .../GanjoorRelatedPersonService.cs | 473 +- 23 files changed, 8908 insertions(+), 82 deletions(-) create mode 100644 GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml create mode 100644 GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml.cs create mode 100644 GanjooRazor/Pages/SuggestNewPersonRelation.cshtml create mode 100644 GanjooRazor/Pages/SuggestNewPersonRelation.cshtml.cs create mode 100644 GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml create mode 100644 GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml.cs create mode 100644 RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.Designer.cs create mode 100644 RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.cs create mode 100644 RMuseum/Models/Ganjoor/GanjoorPersonRelationEditSuggestion.cs create mode 100644 RMuseum/Models/Ganjoor/PersonRelationSuggestionAction.cs diff --git a/GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml b/GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml index f4b40754..55b2c99b 100644 --- a/GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml +++ b/GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml @@ -45,67 +45,77 @@ else } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
فیلدمقدار فعلیپیشنهاد
نام@p.Name@s.SuggestedName
نشانی ویکی‌پدیا@p.WikiUrl@s.SuggestedWikiUrl
سال تولد (قمری)@(p.BirthYearInLHijri == 0 ? "" : p.BirthYearInLHijri.ToPersianNumbers())@(s.SuggestedBirthYearInLHijri == 0 ? "" : s.SuggestedBirthYearInLHijri.ToPersianNumbers())
تاریخ تولد مستند است@(p.ValidBirthDate ? "بله" : "خیر")@(s.SuggestedValidBirthDate ? "بله" : "خیر")
زادگاه@Model.LocationName(p.BirthLocationId)@(s.SuggestedBirthLocation != null ? s.SuggestedBirthLocation.Name : "")
سال وفات (قمری)@(p.DeathYearInLHijri == 0 ? "" : p.DeathYearInLHijri.ToPersianNumbers())@(s.SuggestedDeathYearInLHijri == 0 ? "" : s.SuggestedDeathYearInLHijri.ToPersianNumbers())
تاریخ وفات مستند است@(p.ValidDeathDate ? "بله" : "خیر")@(s.SuggestedValidDeathDate ? "بله" : "خیر")
آرامگاه@Model.LocationName(p.DeathLocationId)@(s.SuggestedDeathLocation != null ? s.SuggestedDeathLocation.Name : "")
عنوان شجره‌نامه@p.FamilyTreeCaption@s.SuggestedFamilyTreeCaption
توضیحات@p.Description@s.SuggestedDescription
+ @if (s.SuggestedForDeletion) + { +
+ این پیشنهاد درخواست حذف کامل شخصیت «@p.Name» است. + در صورت تأیید، این شخصیت و همهٔ نسبت‌های خویشاوندی/وابستگی‌های آن حذف خواهد شد و هرگونه پیشنهاد در حال بررسی مرتبط با آن‌ها به طور خودکار رد می‌شود. فیلدهای زیر در این حالت نادیده گرفته می‌شوند. +
+ } + else + { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
فیلدمقدار فعلیپیشنهاد
نام@p.Name@s.SuggestedName
نشانی ویکی‌پدیا@p.WikiUrl@s.SuggestedWikiUrl
سال تولد (قمری)@(p.BirthYearInLHijri == 0 ? "" : p.BirthYearInLHijri.ToPersianNumbers())@(s.SuggestedBirthYearInLHijri == 0 ? "" : s.SuggestedBirthYearInLHijri.ToPersianNumbers())
تاریخ تولد مستند است@(p.ValidBirthDate ? "بله" : "خیر")@(s.SuggestedValidBirthDate ? "بله" : "خیر")
زادگاه@Model.LocationName(p.BirthLocationId)@(s.SuggestedBirthLocation != null ? s.SuggestedBirthLocation.Name : "")
سال وفات (قمری)@(p.DeathYearInLHijri == 0 ? "" : p.DeathYearInLHijri.ToPersianNumbers())@(s.SuggestedDeathYearInLHijri == 0 ? "" : s.SuggestedDeathYearInLHijri.ToPersianNumbers())
تاریخ وفات مستند است@(p.ValidDeathDate ? "بله" : "خیر")@(s.SuggestedValidDeathDate ? "بله" : "خیر")
آرامگاه@Model.LocationName(p.DeathLocationId)@(s.SuggestedDeathLocation != null ? s.SuggestedDeathLocation.Name : "")
عنوان شجره‌نامه@p.FamilyTreeCaption@s.SuggestedFamilyTreeCaption
توضیحات@p.Description@s.SuggestedDescription
+ }
 
diff --git a/GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml b/GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml new file mode 100644 index 00000000..74ec901f --- /dev/null +++ b/GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml @@ -0,0 +1,199 @@ +@page +@model GanjooRazor.Areas.Admin.Pages.ReviewPersonRelationEditsModel +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using DNTPersianUtils.Core +@using RMuseum.Models.Ganjoor +@{ + Layout = "_AdminLayout"; + ViewData["Title"] = "بازبینی پیشنهادهای نسبت‌های خویشاوندی"; + + string RelationTypeName(PersonRelationType t) + { + switch (t) + { + case PersonRelationType.Parent: return "پدر/مادر و فرزند"; + case PersonRelationType.Sibling: return "خواهر/برادر"; + case PersonRelationType.Spouse: return "همسر"; + case PersonRelationType.Ancestor: return "نیا و نواده"; + default: return t.ToString(); + } + } + + string ActionName(PersonRelationSuggestionAction a) + { + switch (a) + { + case PersonRelationSuggestionAction.Add: return "افزودن نسبت جدید"; + case PersonRelationSuggestionAction.Modify: return "ویرایش نسبت موجود"; + case PersonRelationSuggestionAction.Remove: return "حذف نسبت موجود"; + default: return a.ToString(); + } + } +} + +
+

@ViewData["Title"]

+
+ +@if (!string.IsNullOrEmpty(Model.FatalError)) +{ +
@Model.FatalError
+} +else if (Model.Suggestion == null) +{ + +} +else +{ + var s = Model.Suggestion; + +

+ @ActionName(s.Action) +

+ + + + + + + @if (!string.IsNullOrEmpty(s.SuggestionNote)) + { + + + + + } +
پیشنهاد دهنده: + @(s.User != null ? s.User.NickName : s.UserId.ToString()) + — @s.Date.ToFriendlyPersianDateTextify() +
یادداشت پیشنهاددهنده:@s.SuggestionNote
+ + @if (s.Action == PersonRelationSuggestionAction.Remove) + { +
+ این پیشنهاد درخواست حذف نسبت زیر است: +
+ } + + + + + + + @if (s.Action == PersonRelationSuggestionAction.Modify) + { + + } + + + + + + + @if (s.Action == PersonRelationSuggestionAction.Modify) + { + + } + + + + + @if (s.Action == PersonRelationSuggestionAction.Modify) + { + + } + + + + + @if (s.Action == PersonRelationSuggestionAction.Modify) + { + + } + + + + + @if (s.Action == PersonRelationSuggestionAction.Modify) + { + + } + + + + + @if (s.Action == PersonRelationSuggestionAction.Modify) + { + + } + + +
@(s.Action == PersonRelationSuggestionAction.Remove || s.Action == PersonRelationSuggestionAction.Modify ? "مقدار فعلی" : "پیشنهاد")پیشنهاد
طرف اول + @(s.Person1 != null ? s.Person1.Name : s.Person1Id.ToString()) + + @(s.Person1 != null ? s.Person1.Name : s.Person1Id.ToString()) +
طرف دوم + @(s.Person2 != null ? s.Person2.Name : s.Person2Id.ToString()) + + @(s.Person2 != null ? s.Person2.Name : s.Person2Id.ToString()) +
نوع نسبت@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? RelationTypeName(s.ExistingRelation.RelationType) : RelationTypeName(s.SuggestedRelationType))@RelationTypeName(s.SuggestedRelationType)
درجه (در نیا/نواده)@((s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.DegreeHint : s.SuggestedDegreeHint)?.ToString().ToPersianNumbers())@s.SuggestedDegreeHint?.ToString().ToPersianNumbers()
یادداشت@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.Note : s.SuggestedNote)@s.SuggestedNote
+ +
 
+ + + + + + + + + + + + +
+ +   + +
+ یادداشت برای پیشنهاددهنده:
+ +
+ بررسی + +
+ @Html.AntiForgeryToken() + +
+
+ +

@Model.Skip.ToPersianNumbers() از @Model.TotalCount.ToPersianNumbers()

+ + +} diff --git a/GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml.cs b/GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml.cs new file mode 100644 index 00000000..15c34940 --- /dev/null +++ b/GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml.cs @@ -0,0 +1,155 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using GanjooRazor.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Newtonsoft.Json; +using RMuseum.Models.Ganjoor; +using RMuseum.Models.Ganjoor.ViewModels; +using RSecurityBackend.Models.Generic; + +namespace GanjooRazor.Areas.Admin.Pages +{ + /// + /// moderator review queue for pending GanjoorPersonRelationEditSuggestion entries - counterpart + /// of ReviewPersonEdits.cshtml, but for kinship edges (Add/Modify/Remove) rather than a person's + /// own fields. + /// + public class ReviewPersonRelationEditsModel : PageModel + { + /// + /// the pending suggestion currently shown + /// + public GanjoorPersonRelationEditSuggestion Suggestion { get; set; } + + /// + /// fatal error + /// + public string FatalError { get; set; } + + /// + /// skip + /// + public int Skip { get; set; } + + /// + /// total count + /// + public int TotalCount { get; set; } + + public IActionResult OnPost() + { + Skip = string.IsNullOrEmpty(Request.Query["skip"]) ? 0 : int.Parse(Request.Query["skip"]); + if (Request.Form["next"].Count == 1) + { + return Redirect($"/Admin/ReviewPersonRelationEdits/?skip={Skip + 1}"); + } + return Page(); + } + + public async Task OnGetAsync() + { + if (string.IsNullOrEmpty(Request.Cookies["Token"])) + return Redirect("/"); + + FatalError = ""; + TotalCount = 0; + Skip = string.IsNullOrEmpty(Request.Query["skip"]) ? 0 : int.Parse(Request.Query["skip"]); + + using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response))) + { + if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + { + var nextResponse = await secureClient.GetAsync($"{APIRoot.Url}/api/people/relationeditsuggestions/next?skip={Skip}"); + if (!nextResponse.IsSuccessStatusCode) + { + FatalError = JsonConvert.DeserializeObject(await nextResponse.Content.ReadAsStringAsync()); + return Page(); + } + + string paginationMetadata = nextResponse.Headers.GetValues("paging-headers").FirstOrDefault(); + if (!string.IsNullOrEmpty(paginationMetadata)) + { + TotalCount = JsonConvert.DeserializeObject(paginationMetadata).totalCount; + } + + Suggestion = JsonConvert.DeserializeObject(await nextResponse.Content.ReadAsStringAsync()); + + return Page(); + } + else + { + return Redirect("/"); + } + } + } + + public async Task OnPostModerateAsync([FromBody] PersonRelationEditSuggestionModerationRequest pms) + { + if (string.IsNullOrEmpty(Request.Cookies["Token"])) + return new BadRequestObjectResult("لطفاً از گنجور خارج و مجدداً به آن وارد شوید."); + + if (pms == null || string.IsNullOrEmpty(pms.Result)) + { + return new BadRequestObjectResult("لطفاً تکلیف بررسی این پیشنهاد را مشخص کنید."); + } + + using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response))) + { + if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response)) + { + var moderation = new PersonEditSuggestionModerationViewModel() + { + Result = (CorrectionReviewResult)Enum.Parse(typeof(CorrectionReviewResult), pms.Result), + ReviewNote = pms.ReviewNote, + }; + + var moderationResponse = await secureClient.PostAsync($"{APIRoot.Url}/api/people/relationeditsuggestions/{pms.Id}/moderate", + new StringContent(JsonConvert.SerializeObject(moderation), Encoding.UTF8, "application/json")); + + if (!moderationResponse.IsSuccessStatusCode) + { + string err = await moderationResponse.Content.ReadAsStringAsync(); + if (string.IsNullOrEmpty(err)) + { + if (!string.IsNullOrEmpty(moderationResponse.ReasonPhrase)) + { + err = moderationResponse.ReasonPhrase; + } + else + { + err = $"Error Code: {moderationResponse.StatusCode}"; + } + } + else + { + err = JsonConvert.DeserializeObject(err); + } + return new BadRequestObjectResult(err); + } + + return new OkObjectResult(true); + } + else + { + return new BadRequestObjectResult("لطفاً از گنجور خارج و مجدداً به آن وارد شوید."); + } + } + } + } + + /// + /// JSON body shape posted by ReviewPersonRelationEdits.cshtml's moderatePersonRelationEditSuggestion() + /// ajax call - kept local to this page since it's only ever used to bind that one handler's + /// [FromBody] parameter + /// + public class PersonRelationEditSuggestionModerationRequest + { + public int Id { get; set; } + public string Result { get; set; } + public string ReviewNote { get; set; } + } +} diff --git a/GanjooRazor/Pages/Person.cshtml b/GanjooRazor/Pages/Person.cshtml index 3e5f048d..1b92d03a 100644 --- a/GanjooRazor/Pages/Person.cshtml +++ b/GanjooRazor/Pages/Person.cshtml @@ -69,6 +69,10 @@ else

خویشاوندان نمایش نمودار شجره‌نامه + @if (Model.LoggedIn) + { + افزودن خویشاوند جدید + }

    @foreach (var row in Model.RelationRows) @@ -79,11 +83,25 @@ else { (@row.Note) } + @if (Model.LoggedIn) + { + ✎ + } }
} + else if (Model.LoggedIn) + { +
 
+
+

+ خویشاوندان + افزودن خویشاوند جدید +

+
+ } @if (Model.AffiliationRows.Count > 0) { diff --git a/GanjooRazor/Pages/Person.cshtml.cs b/GanjooRazor/Pages/Person.cshtml.cs index 993edf9a..6b4094b1 100644 --- a/GanjooRazor/Pages/Person.cshtml.cs +++ b/GanjooRazor/Pages/Person.cshtml.cs @@ -41,6 +41,12 @@ namespace GanjooRazor.Pages public class PersonRelationDisplayRow { + /// + /// the underlying GanjoorPersonRelation row's own id - used to link to + /// /SuggestPersonRelationEdit/{RelationId} for suggesting a change/removal of this edge + /// + public int RelationId { get; set; } + /// /// the other person's role relative to the subject (e.g. "فرزند", "پدر/مادر", "نیا") /// @@ -131,6 +137,7 @@ namespace GanjooRazor.Pages { RelationRows.Add(new PersonRelationDisplayRow() { + RelationId = r.Id, Label = RelationLabel(r), OtherPersonId = r.OtherPersonId, OtherPersonName = r.OtherPersonName, diff --git a/GanjooRazor/Pages/Shared/_AdminLayout.cshtml b/GanjooRazor/Pages/Shared/_AdminLayout.cshtml index c43dfed9..00ced0bf 100644 --- a/GanjooRazor/Pages/Shared/_AdminLayout.cshtml +++ b/GanjooRazor/Pages/Shared/_AdminLayout.cshtml @@ -482,6 +482,8 @@ ویرایش‌های قطعات ویرایش‌های زندگینامه‌ها مشق‌ها + ویرایش‌های شخصیت‌ها + ویرایش‌های نسبت‌های خویشاوندی diff --git a/GanjooRazor/Pages/SuggestNewPersonRelation.cshtml b/GanjooRazor/Pages/SuggestNewPersonRelation.cshtml new file mode 100644 index 00000000..3c559e04 --- /dev/null +++ b/GanjooRazor/Pages/SuggestNewPersonRelation.cshtml @@ -0,0 +1,103 @@ +@page "{personId:int}" +@model GanjooRazor.Pages.SuggestNewPersonRelationModel +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@{ + Layout = "_Layout"; + ViewData["Title"] = "گنجور » پیشنهاد خویشاوند جدید"; +} +@section Head { + +} +@if (!string.IsNullOrEmpty(Model.LastError)) +{ + @Model.LastError +} +else +{ + + +
+ +
+ + + +
 
+ +
+

پیشنهاد خویشاوند جدید برای: @Model.Person.Name

+ +

+ این پیشنهاد مستقیماً اعمال نمی‌شود؛ پس از بررسی و تأیید توسط مدیران، به شجره‌نامهٔ + @Model.Person.Name افزوده خواهد شد. +

+ + @if (!string.IsNullOrEmpty(Model.LastResult)) + { +

@Html.Raw(Model.LastResult)

+ } + +
+ @Html.AntiForgeryToken() + + + + + + + + + + + + + + + + + + + + + + +
خویشاوند: + +
نسبت با @Model.Person.Name: + +
+ درجه (فقط برای نیا/نواده): +

مثلاً برای پدربزرگ/مادربزرگ عدد ۲ را وارد کنید. اگر نمی‌دانید خالی بگذارید.

+
یادداشت (مستندسازی):
یادداشت برای مدیر بررسی‌کننده:
+ + + انصراف +
+
+ +
 
+ +
+ + + +
+ + @if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString())) + { + @Html.Raw(ViewData["TrackingScript"]) + } +} diff --git a/GanjooRazor/Pages/SuggestNewPersonRelation.cshtml.cs b/GanjooRazor/Pages/SuggestNewPersonRelation.cshtml.cs new file mode 100644 index 00000000..02289b0a --- /dev/null +++ b/GanjooRazor/Pages/SuggestNewPersonRelation.cshtml.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using GanjooRazor.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using RMuseum.Models.Ganjoor; + +namespace GanjooRazor.Pages +{ + /// + /// contributor-facing form for suggesting a brand new kinship edge between the subject person + /// and another already-approved person - the "Add" counterpart of SuggestPersonRelationEdit.cshtml + /// (which only handles Modify/Remove of an already-existing edge). Presents a direction-aware + /// "RelationKind" choice (e.g. "فرزند" vs "پدر یا مادر") from the subject's point of view, and + /// translates it into the correct Person1Id/Person2Id/RelationType triple before submitting - + /// GanjoorPersonRelation itself has no notion of "from the subject's point of view", only + /// Person1/Person2, so that translation has to happen somewhere, and doing it here keeps the + /// underlying suggestion/moderation code simple and symmetric. + /// + public class SuggestNewPersonRelationModel : LoginPartialEnabledPageModel + { + public SuggestNewPersonRelationModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration) + { + } + + public string LastError { get; set; } + + public string LastResult { get; set; } + + public GanjoorRelatedPerson Person { get; set; } + + public List OtherPeople { get; set; } + + [BindProperty] + public int OtherPersonId { get; set; } + + [BindProperty] + public string RelationKind { get; set; } + + [BindProperty] + public int? DegreeHint { get; set; } + + [BindProperty] + public string Note { get; set; } + + [BindProperty] + public string SuggestionNote { get; set; } + + private async Task PreparePersonAsync(int id) + { + var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/people/{id}"); + if (!response.IsSuccessStatusCode) + { + LastError = await ReadErrorMessageAsync(response); + return false; + } + + Person = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + if (Person == null) + { + LastError = "شخصیتی با این کد پیدا نشد."; + return false; + } + + var peopleResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/people"); + if (!peopleResponse.IsSuccessStatusCode) + { + LastError = await ReadErrorMessageAsync(peopleResponse); + return false; + } + var allPeople = JsonConvert.DeserializeObject>(await peopleResponse.Content.ReadAsStringAsync()); + OtherPeople = allPeople.Where(p => p.Id != id).ToList(); + + return true; + } + + public async Task OnGetAsync(int personId) + { + InitializeCommonPageState(); + + if (!LoggedIn) + { + return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}"); + } + + await PreparePersonAsync(personId); + + return Page(); + } + + public async Task OnPostSuggestAsync(int personId) + { + InitializeCommonPageState(); + + if (!LoggedIn) + { + return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}"); + } + + LastResult = ""; + + if (!await PreparePersonAsync(personId)) + { + return Page(); + } + + if (OtherPersonId == 0 || OtherPersonId == personId) + { + LastResult = "لطفاً خویشاوند مورد نظر را انتخاب کنید."; + return Page(); + } + + // translate the direction-aware choice (from Person's own point of view) into the + // symmetric Person1Id/Person2Id/RelationType triple GanjoorPersonRelation actually stores + var suggestion = new GanjoorPersonRelationEditSuggestion() + { + Action = PersonRelationSuggestionAction.Add, + SuggestedDegreeHint = DegreeHint, + SuggestedNote = string.IsNullOrWhiteSpace(Note) ? null : Note.Trim(), + SuggestionNote = string.IsNullOrWhiteSpace(SuggestionNote) ? null : SuggestionNote.Trim(), + }; + + switch (RelationKind) + { + case "Child": // OtherPerson is a child of Person + suggestion.Person1Id = personId; + suggestion.Person2Id = OtherPersonId; + suggestion.SuggestedRelationType = PersonRelationType.Parent; + break; + case "Parent": // OtherPerson is a parent of Person + suggestion.Person1Id = OtherPersonId; + suggestion.Person2Id = personId; + suggestion.SuggestedRelationType = PersonRelationType.Parent; + break; + case "Sibling": + suggestion.Person1Id = personId; + suggestion.Person2Id = OtherPersonId; + suggestion.SuggestedRelationType = PersonRelationType.Sibling; + break; + case "Spouse": + suggestion.Person1Id = personId; + suggestion.Person2Id = OtherPersonId; + suggestion.SuggestedRelationType = PersonRelationType.Spouse; + break; + case "DistantAncestor": // OtherPerson is a distant ancestor of Person + suggestion.Person1Id = OtherPersonId; + suggestion.Person2Id = personId; + suggestion.SuggestedRelationType = PersonRelationType.Ancestor; + break; + case "DistantDescendant": // OtherPerson is a distant descendant of Person + suggestion.Person1Id = personId; + suggestion.Person2Id = OtherPersonId; + suggestion.SuggestedRelationType = PersonRelationType.Ancestor; + break; + default: + LastResult = "نوع نسبت نامعتبر است."; + return Page(); + } + + using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response))) + { + await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); + + HttpResponseMessage response = await secureClient.PostAsync( + $"{APIRoot.Url}/api/people/relationeditsuggestion", + new StringContent(JsonConvert.SerializeObject(suggestion), Encoding.UTF8, "application/json")); + + if (!response.IsSuccessStatusCode) + { + LastResult = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + return Page(); + } + + LastResult = $"پیشنهاد شما ثبت شد و پس از بررسی توسط مدیران اعمال خواهد شد. بازگشت به صفحهٔ شخصیت"; + + return Page(); + } + } + } +} diff --git a/GanjooRazor/Pages/SuggestPersonEdit.cshtml b/GanjooRazor/Pages/SuggestPersonEdit.cshtml index a3539e83..94376773 100644 --- a/GanjooRazor/Pages/SuggestPersonEdit.cshtml +++ b/GanjooRazor/Pages/SuggestPersonEdit.cshtml @@ -42,6 +42,13 @@ else + + + + diff --git a/GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml b/GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml new file mode 100644 index 00000000..c87d23c0 --- /dev/null +++ b/GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml @@ -0,0 +1,105 @@ +@page "{relationId:int}" +@model GanjooRazor.Pages.SuggestPersonRelationEditModel +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@using RMuseum.Models.Ganjoor +@{ + Layout = "_Layout"; + ViewData["Title"] = "گنجور » پیشنهاد ویرایش نسبت خویشاوندی"; +} +@section Head { + +} +@if (!string.IsNullOrEmpty(Model.LastError)) +{ + @Model.LastError +} +else +{ + + +
+ +
+ + + +
 
+ +
+

+ پیشنهاد ویرایش نسبت: + @(Model.Relation.Person1 != null ? Model.Relation.Person1.Name : Model.Relation.Person1Id.ToString()) + و + @(Model.Relation.Person2 != null ? Model.Relation.Person2.Name : Model.Relation.Person2Id.ToString()) +

+ +

+ این پیشنهاد مستقیماً اعمال نمی‌شود؛ پس از بررسی و تأیید توسط مدیران اعمال خواهد شد. +

+ + @if (!string.IsNullOrEmpty(Model.LastResult)) + { +

@Html.Raw(Model.LastResult)

+ } + +
+ @Html.AntiForgeryToken() + + + + +
پیشنهاد حذف این شخصیت: + +

در صورت انتخاب این گزینه سایر فیلدها نادیده گرفته خواهد شد و در صورت تأیید مدیران، این شخصیت و همهٔ نسبت‌ها/وابستگی‌های آن حذف می‌شود.

+
نام:
+ + + + + + + + + + + + + + + + + + + + +
نوع درخواست: + +   + +
نوع نسبت: + +
+ درجه (فقط برای نیا/نواده): +

مثلاً برای پدربزرگ/مادربزرگ عدد ۲ را وارد کنید. اگر نمی‌دانید خالی بگذارید.

+
یادداشت (مستندسازی):
یادداشت برای مدیر بررسی‌کننده:
+ + + انصراف + + + +
 
+ + + + + + + + @if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString())) + { + @Html.Raw(ViewData["TrackingScript"]) + } +} diff --git a/GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml.cs b/GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml.cs new file mode 100644 index 00000000..456f6c2c --- /dev/null +++ b/GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml.cs @@ -0,0 +1,133 @@ +using System; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using GanjooRazor.Utils; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using RMuseum.Models.Ganjoor; + +namespace GanjooRazor.Pages +{ + /// + /// contributor-facing form for suggesting a change to, or removal of, an existing + /// GanjoorPersonRelation - the kinship-edge counterpart of SuggestPersonEdit.cshtml. Any + /// logged-in user may submit here; nothing is changed until a moderator approves it via + /// Admin/ReviewPersonRelationEdits. Reachable from a person's own public page (Person.cshtml). + /// + public class SuggestPersonRelationEditModel : LoginPartialEnabledPageModel + { + public SuggestPersonRelationEditModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration) + { + } + + public string LastError { get; set; } + + public string LastResult { get; set; } + + public GanjoorPersonRelation Relation { get; set; } + + [BindProperty] + public GanjoorPersonRelationEditSuggestion Suggestion { get; set; } + + [BindProperty] + public string Action { get; set; } // "Modify" or "Remove", from the posted radio buttons + + private async Task PrepareRelationAsync(int relationId) + { + var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/people/relations/{relationId}"); + if (!response.IsSuccessStatusCode) + { + LastError = await ReadErrorMessageAsync(response); + return false; + } + + Relation = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + if (Relation == null) + { + LastError = "نسبتی با این کد پیدا نشد."; + return false; + } + + return true; + } + + private void FillSuggestionFromCurrentRelation() + { + // pre-filled with the relation's CURRENT values, same convention as SuggestPersonEdit - + // submitting a plain "Modify" without changing anything is a harmless no-op + Suggestion = new GanjoorPersonRelationEditSuggestion() + { + Action = PersonRelationSuggestionAction.Modify, + ExistingRelationId = Relation.Id, + Person1Id = Relation.Person1Id, + Person2Id = Relation.Person2Id, + SuggestedRelationType = Relation.RelationType, + SuggestedDegreeHint = Relation.DegreeHint, + SuggestedNote = Relation.Note, + }; + } + + public async Task OnGetAsync(int relationId) + { + InitializeCommonPageState(); + + if (!LoggedIn) + { + return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}"); + } + + if (!await PrepareRelationAsync(relationId)) + { + return Page(); + } + + FillSuggestionFromCurrentRelation(); + + return Page(); + } + + public async Task OnPostSuggestEditAsync(int relationId) + { + InitializeCommonPageState(); + + if (!LoggedIn) + { + return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}"); + } + + LastResult = ""; + + if (!await PrepareRelationAsync(relationId)) + { + return Page(); + } + + Suggestion.ExistingRelationId = Relation.Id; + Suggestion.Action = Action == "Remove" ? PersonRelationSuggestionAction.Remove : PersonRelationSuggestionAction.Modify; + + using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response))) + { + await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response); + + HttpResponseMessage response = await secureClient.PostAsync( + $"{APIRoot.Url}/api/people/relationeditsuggestion", + new StringContent(JsonConvert.SerializeObject(Suggestion), Encoding.UTF8, "application/json")); + + if (!response.IsSuccessStatusCode) + { + LastResult = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + FillSuggestionFromCurrentRelation(); + return Page(); + } + + LastResult = $"پیشنهاد شما ثبت شد و پس از بررسی توسط مدیران اعمال خواهد شد. بازگشت به صفحهٔ شخصیت"; + + FillSuggestionFromCurrentRelation(); + + return Page(); + } + } + } +} diff --git a/RMuseum/Controllers/GanjoorRelatedPersonController.cs b/RMuseum/Controllers/GanjoorRelatedPersonController.cs index 581b67b8..f6962e06 100644 --- a/RMuseum/Controllers/GanjoorRelatedPersonController.cs +++ b/RMuseum/Controllers/GanjoorRelatedPersonController.cs @@ -207,6 +207,100 @@ namespace RMuseum.Controllers return Ok(res.Result); } + /// + /// get a single kinship edge by its own id, with both sides' names resolved - used by + /// /SuggestPersonRelationEdit/{relationId} to show what it's about + /// + /// + /// + [HttpGet("relations/{relationId:int}")] + [AllowAnonymous] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelation))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task GetRelationByIdAsync(int relationId) + { + var res = await _personService.GetRelationByIdAsync(relationId); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + return Ok(res.Result); + } + + /// + /// suggest an addition, change or removal of a kinship edge - any logged-in user, same as + /// suggesting a person edit. Goes into the pending queue; does not change anything. + /// + /// + /// + [HttpPost("relationeditsuggestion")] + [Authorize] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelationEditSuggestion))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task SuggestPersonRelationEditAsync([FromBody] GanjoorPersonRelationEditSuggestion suggestion) + { + suggestion.UserId = new Guid(User.Claims.First(c => c.Type == "UserId").Value); + var res = await _personService.SuggestPersonRelationEditAsync(suggestion); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + return Ok(res.Result); + } + + /// + /// get the next unreviewed relation-edit suggestion, for the moderator queue - same + /// permission as reviewing a person edit + /// + /// + /// + [HttpGet("relationeditsuggestions/next")] + [Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + SecurableItem.ModifyOperationShortName)] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelationEditSuggestion))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task GetNextUnreviewedPersonRelationEditSuggestionAsync(int skip = 0) + { + var res = await _personService.GetNextUnreviewedPersonRelationEditSuggestionAsync(skip); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + + var resCount = await _personService.GetUnreviewedPersonRelationEditSuggestionCountAsync(); + if (!string.IsNullOrEmpty(resCount.ExceptionString)) + return BadRequest(resCount.ExceptionString); + + // Paging Header - same shape ReviewEdits.cshtml.cs already knows how to read for poem corrections + HttpContext.Response.Headers.Append("paging-headers", + JsonConvert.SerializeObject( + new PaginationMetadata() + { + totalCount = resCount.Result, + pageSize = -1, + currentPage = -1, + hasNextPage = false, + hasPreviousPage = false, + totalPages = -1 + }) + ); + + return Ok(res.Result);//might be null + } + + /// + /// apply a moderator's decision to a pending relation-edit suggestion - same permission as + /// moderating a person edit + /// + /// + /// + /// + [HttpPost("relationeditsuggestions/{id:int}/moderate")] + [Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + SecurableItem.ModifyOperationShortName)] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelationEditSuggestion))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task ModeratePersonRelationEditSuggestionAsync(int id, [FromBody] PersonEditSuggestionModerationViewModel moderation) + { + Guid userId = new Guid(User.Claims.First(c => c.Type == "UserId").Value); + var res = await _personService.ModeratePersonRelationEditSuggestionAsync(userId, id, moderation.Result, moderation.ReviewNote); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + return Ok(res.Result); + } + /// /// person service /// diff --git a/RMuseum/DbContext/RMuseumDbContext.cs b/RMuseum/DbContext/RMuseumDbContext.cs index 645bd3e5..e71b939e 100644 --- a/RMuseum/DbContext/RMuseumDbContext.cs +++ b/RMuseum/DbContext/RMuseumDbContext.cs @@ -154,6 +154,29 @@ namespace RMuseum.DbContext .HasForeignKey(a => a.Person2Id) .OnDelete(DeleteBehavior.Restrict); + // same two-required-FKs-to-the-same-table situation as GanjoorPersonRelation above, plus + // a third optional FK to GanjoorPersonRelations itself (ExistingRelationId) - also + // restricted, since a relation that still has a pending suggestion against it should be + // resolved (or the suggestion rejected) before it can be deleted directly, not silently + // orphan the suggestion + builder.Entity() + .HasOne(s => s.Person1) + .WithMany() + .HasForeignKey(s => s.Person1Id) + .OnDelete(DeleteBehavior.Restrict); + + builder.Entity() + .HasOne(s => s.Person2) + .WithMany() + .HasForeignKey(s => s.Person2Id) + .OnDelete(DeleteBehavior.Restrict); + + builder.Entity() + .HasOne(s => s.ExistingRelation) + .WithMany() + .HasForeignKey(s => s.ExistingRelationId) + .OnDelete(DeleteBehavior.Restrict); + builder.Entity() .HasIndex(b => new { b.UserId, b.PoemId, b.CoupletIndex }); @@ -625,6 +648,12 @@ namespace RMuseum.DbContext /// public DbSet GanjoorPersonEditSuggestions { get; set; } + /// + /// pending/reviewed suggested additions, changes or removals of a kinship edge between two + /// already-approved people - see GanjoorPersonRelationEditSuggestion + /// + public DbSet GanjoorPersonRelationEditSuggestions { get; set; } + /// /// Books (PDF Library) /// diff --git a/RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.Designer.cs b/RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.Designer.cs new file mode 100644 index 00000000..fbf5dfcb --- /dev/null +++ b/RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.Designer.cs @@ -0,0 +1,6595 @@ +// +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("20260929160106_AddGanjoorPersonRelationEditSuggestion")] + partial class AddGanjoorPersonRelationEditSuggestion + { + /// + 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("GanjoorCommentId") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("ReactionDate") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("GanjoorCommentId", "UserId") + .IsUnique(); + + b.HasIndex("PoemId", "UserId"); + + b.ToTable("GanjoorCommentReactions"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorDuplicate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DestPoemId") + .HasColumnType("int"); + + b.Property("SrcCatId") + .HasColumnType("int"); + + b.Property("SrcPoemId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DestPoemId"); + + b.HasIndex("SrcPoemId"); + + b.ToTable("GanjoorDuplicates"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorGeoLocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Latitude") + .HasColumnType("decimal(12,9)"); + + b.Property("Longitude") + .HasColumnType("decimal(12,9)"); + + b.Property("MachineGenerated") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GanjoorGeoLocations"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorHealthCheckError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BrokenLink") + .HasColumnType("bit"); + + b.Property("MulipleTargets") + .HasColumnType("bit"); + + b.Property("ReferrerPageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("TargetUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GanjoorHealthCheckErrors"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorLanguage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Code") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("NativeName") + .HasColumnType("nvarchar(max)"); + + b.Property("RightToLeft") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasFilter("[Name] IS NOT NULL"); + + b.ToTable("GanjoorLanguages"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorMetre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm") + .HasColumnType("nvarchar(max)"); + + b.Property("UrlSlug") + .HasColumnType("nvarchar(max)"); + + b.Property("VerseCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("GanjoorMetres"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorNumbering", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EndCatId") + .HasColumnType("int"); + + b.Property("LastCountingDate") + .HasColumnType("datetime2"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("StartCatId") + .HasColumnType("int"); + + b.Property("TotalCouplets") + .HasColumnType("int"); + + b.Property("TotalLines") + .HasColumnType("int"); + + b.Property("TotalParagraphs") + .HasColumnType("int"); + + b.Property("TotalVerses") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("EndCatId"); + + b.HasIndex("StartCatId"); + + b.ToTable("GanjoorNumberings"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPage", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("CatId") + .HasColumnType("int"); + + b.Property("FullTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("FullUrl") + .HasColumnType("nvarchar(450)"); + + b.Property("GanjoorPageType") + .HasColumnType("int"); + + b.Property("HtmlText") + .HasColumnType("nvarchar(max)"); + + b.Property("NoIndex") + .HasColumnType("bit"); + + b.Property("PageOrder") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("PoetId") + .HasColumnType("int"); + + b.Property("PostDate") + .HasColumnType("datetime2"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("RedirectFromFullUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("SecondPoetId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("UrlSlug") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CatId"); + + b.HasIndex("FullUrl"); + + b.HasIndex("ParentId"); + + b.HasIndex("PoemId"); + + b.HasIndex("PoetId"); + + b.HasIndex("SecondPoetId"); + + b.ToTable("GanjoorPages"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPageSnapshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GanjoorPageId") + .HasColumnType("int"); + + b.Property("HtmlText") + .HasColumnType("nvarchar(max)"); + + b.Property("MadeObsoleteByUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("OldTag") + .HasColumnType("nvarchar(max)"); + + b.Property("OldTagPageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RecordDate") + .HasColumnType("datetime2"); + + b.Property("RhymeLetters") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm") + .HasColumnType("nvarchar(max)"); + + b.Property("SourceName") + .HasColumnType("nvarchar(max)"); + + b.Property("SourceUrlSlug") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("UrlSlug") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("GanjoorPageId"); + + b.HasIndex("MadeObsoleteByUserId"); + + b.ToTable("GanjoorPageSnapshots"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonAffiliation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AffiliationType") + .HasColumnType("int"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("Person1Id") + .HasColumnType("int"); + + b.Property("Person2Id") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Person1Id"); + + b.HasIndex("Person2Id"); + + b.ToTable("GanjoorPersonAffiliations"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("PersonId") + .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("SuggestedBirthLocationId") + .HasColumnType("int"); + + b.Property("SuggestedBirthYearInLHijri") + .HasColumnType("int"); + + b.Property("SuggestedDeathLocationId") + .HasColumnType("int"); + + b.Property("SuggestedDeathYearInLHijri") + .HasColumnType("int"); + + b.Property("SuggestedDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedFamilyTreeCaption") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedForDeletion") + .HasColumnType("bit"); + + b.Property("SuggestedName") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedValidBirthDate") + .HasColumnType("bit"); + + b.Property("SuggestedValidDeathDate") + .HasColumnType("bit"); + + b.Property("SuggestedWikiUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestionNote") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PersonId"); + + b.HasIndex("ReviewerUserId"); + + b.HasIndex("SuggestedBirthLocationId"); + + b.HasIndex("SuggestedDeathLocationId"); + + b.HasIndex("UserId"); + + b.ToTable("GanjoorPersonEditSuggestions"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DegreeHint") + .HasColumnType("int"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("Person1Id") + .HasColumnType("int"); + + b.Property("Person2Id") + .HasColumnType("int"); + + b.Property("RelationType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Person1Id"); + + b.HasIndex("Person2Id"); + + b.ToTable("GanjoorPersonRelations"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("ExistingRelationId") + .HasColumnType("int"); + + b.Property("Person1Id") + .HasColumnType("int"); + + b.Property("Person2Id") + .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("SuggestedDegreeHint") + .HasColumnType("int"); + + b.Property("SuggestedNote") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedRelationType") + .HasColumnType("int"); + + b.Property("SuggestionNote") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ExistingRelationId"); + + b.HasIndex("Person1Id"); + + b.HasIndex("Person2Id"); + + b.HasIndex("ReviewerUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GanjoorPersonRelationEditSuggestions"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoem", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("CatId") + .HasColumnType("int"); + + b.Property("ClaimedByMultiplePoets") + .HasColumnType("bit"); + + b.Property("FullTitle") + .HasMaxLength(1500) + .HasColumnType("nvarchar(1500)"); + + b.Property("FullUrl") + .HasColumnType("nvarchar(450)"); + + b.Property("GanjoorMetreId") + .HasColumnType("int"); + + b.Property("HtmlText") + .HasColumnType("nvarchar(max)"); + + b.Property("Language") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("MixedModeOrder") + .HasColumnType("int"); + + b.Property("OldTag") + .HasMaxLength(16) + .HasColumnType("nvarchar(16)"); + + b.Property("OldTagPageUrl") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.Property("PlainText") + .HasColumnType("nvarchar(max)"); + + b.Property("PoemSummary") + .HasMaxLength(3000) + .HasColumnType("nvarchar(3000)"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("RhymeLetters") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("SourceName") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("SourceUrlSlug") + .HasMaxLength(16) + .HasColumnType("nvarchar(16)"); + + b.Property("Title") + .HasMaxLength(1500) + .HasColumnType("nvarchar(1500)"); + + b.Property("UrlSlug") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)"); + + b.HasKey("Id"); + + b.HasIndex("CatId"); + + b.HasIndex("FullUrl"); + + b.HasIndex("GanjoorMetreId"); + + b.HasIndex("Id"); + + b.ToTable("GanjoorPoems"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AffectedThePoem") + .HasColumnType("bit"); + + b.Property("ApplicationOrder") + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("HideMyName") + .HasColumnType("bit"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("LanguageReviewResult") + .HasColumnType("int"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalLanguage") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalPoemFormat") + .HasColumnType("int"); + + b.Property("OriginalPoemSummary") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhymeLetters") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhythm") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhythm2") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhythm3") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhythm4") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("PoemFormat") + .HasColumnType("int"); + + b.Property("PoemFormatReviewResult") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("PoemSummary") + .HasColumnType("nvarchar(max)"); + + 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("RhymeLetters") + .HasColumnType("nvarchar(max)"); + + b.Property("RhymeLettersReviewResult") + .HasColumnType("int"); + + b.Property("Rhythm") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm2") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm2Result") + .HasColumnType("int"); + + b.Property("Rhythm3") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm3Result") + .HasColumnType("int"); + + b.Property("Rhythm4") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm4Result") + .HasColumnType("int"); + + b.Property("RhythmResult") + .HasColumnType("int"); + + b.Property("SummaryReviewResult") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PoemId"); + + b.HasIndex("ReviewerUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GanjoorPoemCorrections"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("ExistingTagId") + .HasColumnType("int"); + + b.Property("GanjoorPoemCorrectionId") + .HasColumnType("int"); + + b.Property("IgnoreInCategory") + .HasColumnType("bit"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LunarDay") + .HasColumnType("int"); + + b.Property("LunarMonth") + .HasColumnType("int"); + + b.Property("LunarYear") + .HasColumnType("int"); + + b.Property("MarkForDelete") + .HasColumnType("bit"); + + b.Property("PersonId") + .HasColumnType("int"); + + b.Property("Result") + .HasColumnType("int"); + + b.Property("ReviewNote") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedLatitude") + .HasColumnType("float"); + + b.Property("SuggestedLocationName") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedLongitude") + .HasColumnType("float"); + + b.Property("SuggestedPersonGraphJson") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestionNote") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("GanjoorPoemCorrectionId"); + + b.HasIndex("LocationId"); + + b.HasIndex("PersonId"); + + b.ToTable("GanjoorPoemGeoDateTagCorrection"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Metre") + .HasColumnType("nvarchar(max)"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("SectionId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PoemId"); + + b.ToTable("GanjoorPoemProbableMetres"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CachedFirstCoupletIndex") + .HasColumnType("int"); + + b.Property("CoupletsCount") + .HasColumnType("int"); + + b.Property("GanjoorMetreId") + .HasColumnType("int"); + + b.Property("GanjoorMetreRefSectionIndex") + .HasColumnType("int"); + + b.Property("HtmlText") + .HasColumnType("nvarchar(max)"); + + b.Property("Index") + .HasColumnType("int"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PlainText") + .HasColumnType("nvarchar(max)"); + + b.Property("PoemFormat") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("PoetId") + .HasColumnType("int"); + + b.Property("RhymeLetters") + .HasColumnType("nvarchar(450)"); + + b.Property("SectionType") + .HasColumnType("int"); + + b.Property("VerseType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PoetId"); + + b.HasIndex("RhymeLetters"); + + b.HasIndex("GanjoorMetreId", "RhymeLetters"); + + b.HasIndex("PoemId", "Index"); + + b.HasIndex("GanjoorMetreId", "RhymeLetters", "Id"); + + b.HasIndex("GanjoorMetreId", "RhymeLetters", "SectionType"); + + b.ToTable("GanjoorPoemSections"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemSectionCorrection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AffectedThePoem") + .HasColumnType("bit"); + + b.Property("ApplicationOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse10VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse10VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse1VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse1VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse2VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse2VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse3VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse3VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse4VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse4VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse5VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse5VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse6VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse6VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse7VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse7VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse8VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse8VOrderResult") + .HasColumnType("int"); + + b.Property("BreakFromVerse9VOrder") + .HasColumnType("int"); + + b.Property("BreakFromVerse9VOrderResult") + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("HideMyName") + .HasColumnType("bit"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("LanguageReviewResult") + .HasColumnType("int"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalLanguage") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalPoemFormat") + .HasColumnType("int"); + + b.Property("OriginalRhymeLetters") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhythm") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalRhythm2") + .HasColumnType("nvarchar(max)"); + + b.Property("PoemFormat") + .HasColumnType("int"); + + b.Property("PoemFormatReviewResult") + .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("RhymeLetters") + .HasColumnType("nvarchar(max)"); + + b.Property("RhymeLettersReviewResult") + .HasColumnType("int"); + + b.Property("Rhythm") + .HasColumnType("nvarchar(max)"); + + b.Property("Rhythm2") + .HasColumnType("nvarchar(max)"); + + b.Property("RhythmResult") + .HasColumnType("int"); + + b.Property("RhythmResult2") + .HasColumnType("int"); + + b.Property("SectionId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ReviewerUserId"); + + b.HasIndex("SectionId"); + + b.HasIndex("UserId"); + + b.ToTable("GanjoorPoemSectionCorrections"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoet", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("BirthLocationId") + .HasColumnType("int"); + + b.Property("BirthYearInLHijri") + .HasColumnType("int"); + + b.Property("DeathLocationId") + .HasColumnType("int"); + + b.Property("DeathYearInLHijri") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Nickname") + .HasColumnType("nvarchar(max)"); + + b.Property("PinOrder") + .HasColumnType("int"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("RImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("ValidBirthDate") + .HasColumnType("bit"); + + b.Property("ValidDeathDate") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("BirthLocationId"); + + b.HasIndex("DeathLocationId"); + + b.HasIndex("RImageId"); + + b.HasIndex("Published", "Id"); + + SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex("Published", "Id"), new[] { "Name", "Nickname", "RImageId" }); + + b.ToTable("GanjoorPoets"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoetSuggestedPicture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChosenOne") + .HasColumnType("bit"); + + b.Property("PicOrder") + .HasColumnType("int"); + + b.Property("PictureId") + .HasColumnType("uniqueidentifier"); + + b.Property("PoetId") + .HasColumnType("int"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PictureId"); + + b.HasIndex("PoetId"); + + b.HasIndex("SuggestedById"); + + b.ToTable("GanjoorPoetSuggestedPictures"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoetSuggestedSpecLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Contents") + .HasColumnType("nvarchar(max)"); + + b.Property("LineOrder") + .HasColumnType("int"); + + b.Property("PoetId") + .HasColumnType("int"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PoetId"); + + b.HasIndex("SuggestedById"); + + b.ToTable("GanjoorPoetSuggestedSpecLines"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorQuotedPoem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CachedRelatedPoemFullTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("CachedRelatedPoemFullUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("CachedRelatedPoemPoetDeathYearInLHijri") + .HasColumnType("int"); + + b.Property("CachedRelatedPoemPoetImage") + .HasColumnType("nvarchar(max)"); + + b.Property("CachedRelatedPoemPoetName") + .HasColumnType("nvarchar(max)"); + + b.Property("CachedRelatedPoemPoetUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ChosenForMainList") + .HasColumnType("bit"); + + b.Property("ClaimedByBothPoets") + .HasColumnType("bit"); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("CoupletVerse1") + .HasColumnType("nvarchar(max)"); + + b.Property("CoupletVerse1ShouldBeEmphasized") + .HasColumnType("bit"); + + b.Property("CoupletVerse2") + .HasColumnType("nvarchar(max)"); + + b.Property("CoupletVerse2ShouldBeEmphasized") + .HasColumnType("bit"); + + b.Property("IndirectQuotation") + .HasColumnType("bit"); + + b.Property("IsPriorToRelated") + .HasColumnType("bit"); + + b.Property("Note") + .HasColumnType("nvarchar(max)"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("PoetId") + .HasColumnType("int"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("Rejected") + .HasColumnType("bit"); + + b.Property("RelatedCoupletIndex") + .HasColumnType("int"); + + b.Property("RelatedCoupletVerse1") + .HasColumnType("nvarchar(max)"); + + b.Property("RelatedCoupletVerse1ShouldBeEmphasized") + .HasColumnType("bit"); + + b.Property("RelatedCoupletVerse2") + .HasColumnType("nvarchar(max)"); + + b.Property("RelatedCoupletVerse2ShouldBeEmphasized") + .HasColumnType("bit"); + + b.Property("RelatedPoemId") + .HasColumnType("int"); + + b.Property("RelatedPoetId") + .HasColumnType("int"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("ReviewNote") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewerUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("SamePoemsQuotedCount") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestionDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("PoemId"); + + b.HasIndex("ReviewerUserId"); + + b.HasIndex("SuggestedById"); + + b.ToTable("GanjoorQuotedPoems"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BirthLocationId") + .HasColumnType("int"); + + b.Property("BirthYearInLHijri") + .HasColumnType("int"); + + b.Property("DeathLocationId") + .HasColumnType("int"); + + b.Property("DeathYearInLHijri") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("FamilyTreeCaption") + .HasColumnType("nvarchar(max)"); + + b.Property("MachineGenerated") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ValidBirthDate") + .HasColumnType("bit"); + + b.Property("ValidDeathDate") + .HasColumnType("bit"); + + b.Property("WikiUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BirthLocationId"); + + b.HasIndex("DeathLocationId"); + + b.ToTable("GanjoorRelatedPersons"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorSiteBanner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("AlternateText") + .HasColumnType("nvarchar(max)"); + + b.Property("RImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("TargetUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RImageId"); + + b.ToTable("GanjoorSiteBanners"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikCat", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("PoetId") + .HasColumnType("int"); + + b.Property("TajikDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("TajikTitle") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("PoetId"); + + b.ToTable("TajikCats"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikPage", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("TajikHtmlText") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TajikPages"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikPoem", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("CatId") + .HasColumnType("int"); + + b.Property("FullTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("FullUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("TajikPlainText") + .HasColumnType("nvarchar(max)"); + + b.Property("TajikTitle") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CatId"); + + b.ToTable("TajikPoems"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikPoet", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("BirthYearInLHijri") + .HasColumnType("int"); + + b.Property("TajikDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("TajikNickname") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TajikPoets"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikVerse", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("TajikText") + .HasColumnType("nvarchar(max)"); + + b.Property("VOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TajikVerses"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorUserBookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("PrivateNote") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Verse2Id") + .HasColumnType("int"); + + b.Property("VerseId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PoemId"); + + b.HasIndex("UserId", "PoemId", "CoupletIndex"); + + b.ToTable("GanjoorUserBookmarks"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorUserPoemVisit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Counter") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PoemId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "PoemId"); + + b.ToTable("GanjoorUserPoemVisits"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorVerse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("CoupletSummary") + .HasMaxLength(4000) + .HasColumnType("nvarchar(4000)"); + + b.Property("LanguageId") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("SectionIndex1") + .HasColumnType("int"); + + b.Property("SectionIndex2") + .HasColumnType("int"); + + b.Property("SectionIndex3") + .HasColumnType("int"); + + b.Property("SectionIndex4") + .HasColumnType("int"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.Property("VOrder") + .HasColumnType("int"); + + b.Property("VersePosition") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LanguageId"); + + b.HasIndex("PoemId"); + + b.HasIndex("PoemId", "CoupletIndex"); + + b.ToTable("GanjoorVerses"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorVerseNumber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("IsPoemVerse") + .HasColumnType("bit"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("NumberingId") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("SameTypeNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("PoemId", "CoupletIndex"); + + b.HasIndex("NumberingId", "PoemId", "CoupletIndex") + .IsUnique(); + + b.ToTable("GanjoorVerseNumbers"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorVerseVOrderText", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("CoupletSummary") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorPoemCorrectionId") + .HasColumnType("int"); + + b.Property("LanguageId") + .HasColumnType("int"); + + b.Property("LanguageReviewResult") + .HasColumnType("int"); + + b.Property("MarkForDelete") + .HasColumnType("bit"); + + b.Property("MarkForDeleteResult") + .HasColumnType("int"); + + b.Property("NewVerse") + .HasColumnType("bit"); + + b.Property("NewVerseResult") + .HasColumnType("int"); + + b.Property("OriginalCoupletSummary") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalLanguageId") + .HasColumnType("int"); + + b.Property("OriginalText") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalVersePosition") + .HasColumnType("int"); + + b.Property("Result") + .HasColumnType("int"); + + b.Property("ReviewNote") + .HasColumnType("nvarchar(max)"); + + b.Property("SummaryReviewResult") + .HasColumnType("int"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.Property("VORder") + .HasColumnType("int"); + + b.Property("VersePosition") + .HasColumnType("int"); + + b.Property("VersePositionResult") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GanjoorPoemCorrectionId"); + + b.ToTable("GanjoorVerseVOrderText"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.PoemGeoDateTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("IgnoreInCategory") + .HasColumnType("bit"); + + b.Property("LocationId") + .HasColumnType("int"); + + b.Property("LunarDateTotalNumber") + .HasColumnType("int"); + + b.Property("LunarDay") + .HasColumnType("int"); + + b.Property("LunarMonth") + .HasColumnType("int"); + + b.Property("LunarYear") + .HasColumnType("int"); + + b.Property("MachineGenerated") + .HasColumnType("bit"); + + b.Property("PersonId") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("VerifiedDate") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("LocationId"); + + b.HasIndex("PersonId"); + + b.HasIndex("PoemId"); + + b.ToTable("PoemGeoDateTags"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.PoemMusicTrack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AlbumName") + .HasColumnType("nvarchar(max)"); + + b.Property("AlbumUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ApprovalDate") + .HasColumnType("datetime2"); + + b.Property("Approved") + .HasColumnType("bit"); + + b.Property("ArtistName") + .HasColumnType("nvarchar(max)"); + + b.Property("ArtistUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("BrokenLink") + .HasColumnType("bit"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorTrackId") + .HasColumnType("int"); + + b.Property("GolhaTrackId") + .HasColumnType("int"); + + b.Property("PoemId") + .HasColumnType("int"); + + b.Property("Rejected") + .HasColumnType("bit"); + + b.Property("RejectionCause") + .HasColumnType("nvarchar(max)"); + + b.Property("SingerId") + .HasColumnType("int"); + + b.Property("SongOrder") + .HasColumnType("int"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.Property("TrackName") + .HasColumnType("nvarchar(max)"); + + b.Property("TrackType") + .HasColumnType("int"); + + b.Property("TrackUrl") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("GanjoorTrackId"); + + b.HasIndex("GolhaTrackId"); + + b.HasIndex("PoemId"); + + b.HasIndex("SingerId"); + + b.HasIndex("SuggestedById"); + + b.HasIndex("Approved", "Rejected"); + + b.ToTable("GanjoorPoemMusicTracks"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.SemanticSearch.SemanticSearchQueryLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClickedAtUtc") + .HasColumnType("datetime2"); + + b.Property("ClickedPoemId") + .HasColumnType("int"); + + b.Property("ClickedResultRank") + .HasColumnType("int"); + + b.Property("DateTimeUtc") + .HasColumnType("datetime2"); + + b.Property("DetectedCategoryName") + .HasColumnType("nvarchar(max)"); + + b.Property("DetectedPoetName") + .HasColumnType("nvarchar(max)"); + + b.Property("Query") + .HasColumnType("nvarchar(max)"); + + b.Property("RequestedTopK") + .HasColumnType("int"); + + b.Property("ResultCount") + .HasColumnType("int"); + + b.Property("ScopeDetected") + .HasColumnType("bit"); + + b.Property("ScopeDetectionDisabled") + .HasColumnType("bit"); + + b.Property("TopResultScore") + .HasColumnType("real"); + + b.HasKey("Id"); + + b.ToTable("SemanticSearchQueryLogs"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.UpdatingRelSectsLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("MeterId") + .HasColumnType("int"); + + b.Property("RhymeLettes") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("UpdatingRelSectsLogs"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.Recitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AudioArtist") + .HasColumnType("nvarchar(max)"); + + b.Property("AudioArtistUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("AudioOrder") + .HasColumnType("int"); + + b.Property("AudioSrc") + .HasColumnType("nvarchar(max)"); + + b.Property("AudioSrcUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("AudioSyncStatus") + .HasColumnType("int"); + + b.Property("AudioTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("FileLastUpdated") + .HasColumnType("datetime2"); + + b.Property("FileNameWithoutExtension") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorAudioId") + .HasColumnType("int"); + + b.Property("GanjoorPostId") + .HasColumnType("int"); + + b.Property("InSyncWithText") + .HasColumnType("bit"); + + b.Property("InitialScore") + .HasColumnType("int"); + + b.Property("LegacyAudioGuid") + .HasColumnType("uniqueidentifier"); + + b.Property("LocalMp3FilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("LocalXmlFilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("Mp3FileCheckSum") + .HasColumnType("nvarchar(max)"); + + b.Property("Mp3SizeInBytes") + .HasColumnType("int"); + + b.Property("OggSizeInBytes") + .HasColumnType("int"); + + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("RecitationType") + .HasColumnType("int"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("ReviewMsg") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewStatus") + .HasColumnType("int"); + + b.Property("ReviewerId") + .HasColumnType("uniqueidentifier"); + + b.Property("SoundFilesFolder") + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("GanjoorAudioId"); + + b.HasIndex("GanjoorPostId"); + + b.HasIndex("OwnerId"); + + b.HasIndex("ReviewerId"); + + b.HasIndex("ReviewStatus", "GanjoorPostId"); + + b.ToTable("Recitations"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationApprovedMistake", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("Mistake") + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfLinesAffected") + .HasColumnType("int"); + + b.Property("RecitationId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RecitationId"); + + b.ToTable("RecitationApprovedMistakes"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationErrorReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoupletIndex") + .HasColumnType("int"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("NumberOfLinesAffected") + .HasColumnType("int"); + + b.Property("ReasonText") + .HasColumnType("nvarchar(max)"); + + b.Property("RecitationId") + .HasColumnType("int"); + + b.Property("ReporterId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RecitationId"); + + b.HasIndex("ReporterId"); + + b.ToTable("RecitationErrorReports"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationPublishingTracker", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FinishDate") + .HasColumnType("datetime2"); + + b.Property("Finished") + .HasColumnType("bit"); + + b.Property("FirstDbUpdated") + .HasColumnType("bit"); + + b.Property("LastException") + .HasColumnType("nvarchar(max)"); + + b.Property("Mp3FileCopied") + .HasColumnType("bit"); + + b.Property("PoemNarrationId") + .HasColumnType("int"); + + b.Property("SecondDbUpdated") + .HasColumnType("bit"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("XmlFileCopied") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("PoemNarrationId"); + + b.ToTable("RecitationPublishingTrackers"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationUserUpVote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("RecitationId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("RecitationId", "UserId") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("RecitationUserUpVotes"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.UserRecitationProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ArtistName") + .HasColumnType("nvarchar(max)"); + + b.Property("ArtistUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("AudioSrc") + .HasColumnType("nvarchar(max)"); + + b.Property("AudioSrcUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("FileSuffixWithoutDash") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserRecitationProfiles"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.GanjoorLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ArtifactId") + .HasColumnType("uniqueidentifier"); + + b.Property("DisplayOnPage") + .HasColumnType("bit"); + + b.Property("GanjoorPostId") + .HasColumnType("int"); + + b.Property("GanjoorTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("IsTextOriginalSource") + .HasColumnType("bit"); + + b.Property("ItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkToOriginalSource") + .HasColumnType("bit"); + + b.Property("OriginalSourceUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("ReviewResult") + .HasColumnType("int"); + + b.Property("ReviewerId") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestionDate") + .HasColumnType("datetime2"); + + b.Property("Synchronized") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ArtifactId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReviewerId"); + + b.HasIndex("SuggestedById"); + + b.ToTable("GanjoorLinks"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.GanjoorPaperSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BookFullTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("BookFullUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("BookType") + .HasColumnType("int"); + + b.Property("CoverThumbnailImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorCatFullTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorCatFullUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorCatId") + .HasColumnType("int"); + + b.Property("GanjoorPoetId") + .HasColumnType("int"); + + b.Property("HumanReviewed") + .HasColumnType("bit"); + + b.Property("IsTextOriginalSource") + .HasColumnType("bit"); + + b.Property("MatchPercent") + .HasColumnType("int"); + + b.Property("NaskbanBookId") + .HasColumnType("int"); + + b.Property("OrderIndicator") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("GanjoorPaperSources"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.PDFGanjoorLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ExternalThumbnailImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorPostId") + .HasColumnType("int"); + + b.Property("GanjoorTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("IsTextOriginalSource") + .HasColumnType("bit"); + + b.Property("PDFBookId") + .HasColumnType("int"); + + b.Property("PDFPageTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("PageNumber") + .HasColumnType("int"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("ReviewResult") + .HasColumnType("int"); + + b.Property("ReviewerId") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestedByMachine") + .HasColumnType("bit"); + + b.Property("SuggestionDate") + .HasColumnType("datetime2"); + + b.Property("Synchronized") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ReviewerId"); + + b.HasIndex("SuggestedById"); + + b.ToTable("PDFGanjoorLinks"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.PinterestLink", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AltText") + .HasColumnType("nvarchar(max)"); + + b.Property("ArtifactId") + .HasColumnType("uniqueidentifier"); + + b.Property("GanjoorPostId") + .HasColumnType("int"); + + b.Property("GanjoorTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("GanjoorUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("HumanReviewed") + .HasColumnType("bit"); + + b.Property("IsTextOriginalSource") + .HasColumnType("bit"); + + b.Property("ItemId") + .HasColumnType("uniqueidentifier"); + + b.Property("LinkType") + .HasColumnType("int"); + + b.Property("MatchPercent") + .HasColumnType("int"); + + b.Property("NaskbanLinkId") + .HasColumnType("uniqueidentifier"); + + b.Property("PDFBookId") + .HasColumnType("int"); + + b.Property("PageNumber") + .HasColumnType("int"); + + b.Property("PinterestImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("PinterestUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewDate") + .HasColumnType("datetime2"); + + b.Property("ReviewDesc") + .HasColumnType("nvarchar(max)"); + + b.Property("ReviewResult") + .HasColumnType("int"); + + b.Property("ReviewerId") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestedById") + .HasColumnType("uniqueidentifier"); + + b.Property("SuggestionDate") + .HasColumnType("datetime2"); + + b.Property("Synchronized") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ArtifactId"); + + b.HasIndex("ItemId"); + + b.HasIndex("ReviewerId"); + + b.HasIndex("SuggestedById"); + + b.ToTable("PinterestLinks"); + }); + + modelBuilder.Entity("RMuseum.Models.ImportJob.ImportJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ArtifactId") + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("FriendlyUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("JobType") + .HasColumnType("int"); + + b.Property("ProgressPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("QueueTime") + .HasColumnType("datetime2"); + + b.Property("ResourceNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("SrcContent") + .HasColumnType("nvarchar(max)"); + + b.Property("SrcUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ArtifactId"); + + b.ToTable("ImportJobs"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorAlbum", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("SingerId") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("SingerId"); + + b.ToTable("GanjoorAlbum"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorSinger", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("RImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("RImageId"); + + b.ToTable("GanjoorSingers"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorTrack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AlbumId") + .HasColumnType("int"); + + b.Property("BlockReason") + .HasColumnType("nvarchar(max)"); + + b.Property("Blocked") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AlbumId"); + + b.HasIndex("Name"); + + b.ToTable("GanjoorMusicCatalogueTracks"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaCollection", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GolhaCollections"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaProgram", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("GolhaCollectionId") + .HasColumnType("int"); + + b.Property("Mp3") + .HasColumnType("nvarchar(max)"); + + b.Property("ProgramOrder") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("GolhaCollectionId"); + + b.ToTable("GolhaPrograms"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaTrack", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("BlockReason") + .HasColumnType("nvarchar(max)"); + + b.Property("Blocked") + .HasColumnType("bit"); + + b.Property("GolhaProgramId") + .HasColumnType("int"); + + b.Property("SingerId") + .HasColumnType("int"); + + b.Property("Timing") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("TrackNo") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GolhaProgramId"); + + b.HasIndex("SingerId"); + + b.ToTable("GolhaTracks"); + }); + + modelBuilder.Entity("RMuseum.Models.Note.RUserNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("Modified") + .HasColumnType("bit"); + + b.Property("NoteType") + .HasColumnType("int"); + + b.Property("RAppUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RArtifactItemRecordId") + .HasColumnType("uniqueidentifier"); + + b.Property("RArtifactMasterRecordId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReferenceNoteId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RAppUserId"); + + b.HasIndex("RArtifactItemRecordId"); + + b.HasIndex("RArtifactMasterRecordId"); + + b.HasIndex("ReferenceNoteId"); + + b.ToTable("UserNotes"); + }); + + modelBuilder.Entity("RMuseum.Models.Note.RUserNoteAbuseReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("NoteId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReasonText") + .HasColumnType("nvarchar(max)"); + + b.Property("ReporterId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NoteId"); + + b.HasIndex("ReporterId"); + + b.ToTable("ReportedUserNotes"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.Author", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Bio") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtenalImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("NameInOriginalLanguage") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.AuthorRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorId") + .HasColumnType("int"); + + b.Property("BookId") + .HasColumnType("int"); + + b.Property("PDFBookId") + .HasColumnType("int"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.HasIndex("BookId"); + + b.HasIndex("PDFBookId"); + + b.ToTable("AuthorRole"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CoverImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtenalCoverImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CoverImageId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.MultiVolumePDFCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BookId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("VolumeCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BookId"); + + b.ToTable("MultiVolumePDFCollections"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.OCRQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("PDFBookId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("OCRQueuedItems"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFBook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorsLine") + .HasColumnType("nvarchar(max)"); + + b.Property("BookId") + .HasColumnType("int"); + + b.Property("BookScriptType") + .HasColumnType("int"); + + b.Property("BookText") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimedPageCount") + .HasColumnType("int"); + + b.Property("CoverImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtenalCoverImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalPDFFileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("FileMD5CheckSum") + .HasColumnType("nvarchar(max)"); + + b.Property("ISBN") + .HasColumnType("nvarchar(max)"); + + b.Property("IsTranslation") + .HasColumnType("bit"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("MultiVolumePDFCollectionId") + .HasColumnType("int"); + + b.Property("OCRTime") + .HasColumnType("datetime2"); + + b.Property("OCRed") + .HasColumnType("bit"); + + b.Property("OriginalFileName") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalFileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalSourceName") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalSourceUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("PDFFileId") + .HasColumnType("uniqueidentifier"); + + b.Property("PDFSourceId") + .HasColumnType("int"); + + b.Property("PageCount") + .HasColumnType("int"); + + b.Property("PublisherLine") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishingDate") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishingLocation") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishingNumber") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StorageFolderName") + .HasColumnType("nvarchar(max)"); + + b.Property("SubTitle") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("TitleInOriginalLanguage") + .HasColumnType("nvarchar(max)"); + + b.Property("TranslatorsLine") + .HasColumnType("nvarchar(max)"); + + b.Property("VolumeOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BookId"); + + b.HasIndex("CoverImageId"); + + b.HasIndex("MultiVolumePDFCollectionId"); + + b.HasIndex("PDFFileId"); + + b.HasIndex("PDFSourceId"); + + b.ToTable("PDFBooks"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtenalThumbnailImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("FullResolutionImageHeight") + .HasColumnType("int"); + + b.Property("FullResolutionImageWidth") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("OCRTime") + .HasColumnType("datetime2"); + + b.Property("OCRed") + .HasColumnType("bit"); + + b.Property("PDFBookId") + .HasColumnType("int"); + + b.Property("PageNumber") + .HasColumnType("int"); + + b.Property("PageText") + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailImageId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PDFBookId"); + + b.HasIndex("ThumbnailImageId"); + + b.ToTable("PDFPages"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("PDFSources"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.QueuedPDFBook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AuthorsLine") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DownloadOrder") + .HasColumnType("int"); + + b.Property("IsTranslation") + .HasColumnType("bit"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalFileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalSourceName") + .HasColumnType("nvarchar(max)"); + + b.Property("OriginalSourceUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessResult") + .HasColumnType("nvarchar(max)"); + + b.Property("Processed") + .HasColumnType("bit"); + + b.Property("ResultId") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("TranslatorsLine") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("QueuedPDFBooks"); + }); + + modelBuilder.Entity("RMuseum.Models.UploadSession.UploadSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ProcessEndTime") + .HasColumnType("datetime2"); + + b.Property("ProcessProgress") + .HasColumnType("int"); + + b.Property("ProcessStartTime") + .HasColumnType("datetime2"); + + b.Property("SessionType") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UploadEndTime") + .HasColumnType("datetime2"); + + b.Property("UploadStartTime") + .HasColumnType("datetime2"); + + b.Property("UseId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UploadSessions"); + }); + + modelBuilder.Entity("RMuseum.Models.UploadSession.UploadSessionFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentDisposition") + .HasColumnType("nvarchar(max)"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("FilePath") + .HasColumnType("nvarchar(max)"); + + b.Property("Length") + .HasColumnType("bigint"); + + b.Property("MP3FileCheckSum") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessResult") + .HasColumnType("bit"); + + b.Property("ProcessResultMsg") + .HasColumnType("nvarchar(max)"); + + b.Property("UploadSessionId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UploadSessionId"); + + b.ToTable("UploadedFiles"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Audit.Db.REvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("EventType") + .HasColumnType("nvarchar(max)"); + + b.Property("IpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("JsonData") + .HasColumnType("nvarchar(max)"); + + b.Property("RequestUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ResponseStatusCode") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AuditLogs"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.BannedEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("BannedEmails"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RAppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RAppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Bio") + .HasColumnType("nvarchar(max)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("LockoutMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("NickName") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("RImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("ScreenName") + .HasColumnType("nvarchar(max)"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SurName") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Website") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.HasIndex("RImageId"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RCaptchaImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("RImageId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RImageId"); + + b.ToTable("CaptchaImages"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RPermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("OperationShortName") + .HasColumnType("nvarchar(max)"); + + b.Property("RAppRoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("SecurableItemShortName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RAppRoleId"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RTemporaryUserSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClientAppName") + .HasColumnType("nvarchar(max)"); + + b.Property("ClientIPAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("LastRenewal") + .HasColumnType("datetime2"); + + b.Property("LoginTime") + .HasColumnType("datetime2"); + + b.Property("RAppUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Token") + .HasColumnType("nvarchar(max)"); + + b.Property("ValidUntil") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("RAppUserId"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RUserBehaviourLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserBehaviourLogs"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RVerifyQueueItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ClientAppName") + .HasColumnType("nvarchar(max)"); + + b.Property("ClientIPAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Language") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("QueueType") + .HasColumnType("int"); + + b.Property("Secret") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Secret") + .IsUnique() + .HasFilter("[Secret] IS NOT NULL"); + + b.ToTable("VerifyQueueItems"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.UserOldContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ChangeDate") + .HasColumnType("datetime2"); + + b.Property("IsEmail") + .HasColumnType("bit"); + + b.Property("NormalizedValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserOldContacts"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.ChangeTracking.RChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("EntityId") + .HasColumnType("int"); + + b.Property("EntityJson") + .HasColumnType("nvarchar(max)"); + + b.Property("EntityName") + .HasMaxLength(16) + .HasColumnType("nvarchar(16)"); + + b.Property("EntityUId") + .HasColumnType("uniqueidentifier"); + + b.Property("Operation") + .HasMaxLength(24) + .HasColumnType("nvarchar(24)"); + + b.Property("RAppUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkspaceId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RAppUserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("RChangeLogs"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSPermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("OperationShortName") + .HasColumnType("nvarchar(max)"); + + b.Property("RWSRoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("SecurableItemShortName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RWSRoleId"); + + b.ToTable("RWSPermissions"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(max)"); + + b.Property("WorkspaceId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("RWSRoles"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("InviteDate") + .HasColumnType("datetime2"); + + b.Property("MemberFrom") + .HasColumnType("datetime2"); + + b.Property("RAppUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RWorkspaceId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WorkspaceOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("RAppUserId"); + + b.HasIndex("RWorkspaceId"); + + b.ToTable("RWSUsers"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSUserRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkspaceId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("RWSUserRoles"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWorkspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("CreateDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("RWorkspaces"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.WorkspaceUserInvitation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("WorkspaceId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("WorkspaceUserInvitations"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Generic.Db.RGenericOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("RAppUserId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.Property("WorkspaceId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("RAppUserId", "Name") + .IsUnique() + .HasFilter("[RAppUserId] IS NOT NULL AND [Name] IS NOT NULL"); + + b.ToTable("Options"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Generic.Db.RLongRunningJobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("Exception") + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdate") + .HasColumnType("datetime2"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Progress") + .HasColumnType("float"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("Step") + .HasColumnType("nvarchar(max)"); + + b.Property("Succeeded") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.ToTable("LongRunningJobs"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Image.RImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("DataTime") + .HasColumnType("datetime2"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(13) + .HasColumnType("nvarchar(13)"); + + b.Property("FileSizeInBytes") + .HasColumnType("bigint"); + + b.Property("FolderName") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageHeight") + .HasColumnType("int"); + + b.Property("ImageWidth") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("OriginalFileName") + .HasColumnType("nvarchar(max)"); + + b.Property("StoredFileName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("GeneralImages"); + + b.HasDiscriminator("Discriminator").HasValue("RImage"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Notification.RUserNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("DateTime") + .HasColumnType("datetime2"); + + b.Property("HtmlText") + .HasColumnType("nvarchar(max)"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RPictureFile", b => + { + b.HasBaseType("RSecurityBackend.Models.Image.RImage"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("DescriptionInEnglish") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalNormalSizeImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("LastModifiedMeta") + .HasColumnType("datetime2"); + + b.Property("NormalSizeImageHeight") + .HasColumnType("int"); + + b.Property("NormalSizeImageStoredFileName") + .HasColumnType("nvarchar(max)"); + + b.Property("NormalSizeImageWidth") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RArtifactItemRecordId") + .HasColumnType("uniqueidentifier"); + + b.Property("SrcUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("ThumbnailImageHeight") + .HasColumnType("int"); + + b.Property("ThumbnailImageStoredFileName") + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailImageWidth") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("TitleInEnglish") + .HasColumnType("nvarchar(max)"); + + b.HasIndex("RArtifactItemRecordId"); + + b.HasDiscriminator().HasValue("RPictureFile"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RMuseum.Models.Accounting.DonationExpenditure", b => + { + b.HasOne("RMuseum.Models.Accounting.GanjoorDonation", "GanjoorDonation") + .WithMany() + .HasForeignKey("GanjoorDonationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.Accounting.GanjoorExpense", null) + .WithMany("DonationExpenditures") + .HasForeignKey("GanjoorExpenseId"); + + b.Navigation("GanjoorDonation"); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RArtifactItemRecord", b => + { + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", null) + .WithMany("Items") + .HasForeignKey("RArtifactMasterRecordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RArtifactMasterRecord", b => + { + b.HasOne("RMuseum.Models.Artifact.RPictureFile", "CoverImage") + .WithMany() + .HasForeignKey("CoverImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CoverImage"); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RTagValue", b => + { + b.HasOne("RMuseum.Models.PDFLibrary.Book", null) + .WithMany("Tags") + .HasForeignKey("BookId"); + + b.HasOne("RMuseum.Models.PDFLibrary.PDFBook", null) + .WithMany("Tags") + .HasForeignKey("PDFBookId"); + + b.HasOne("RMuseum.Models.PDFLibrary.PDFPage", null) + .WithMany("Tags") + .HasForeignKey("PDFPageId"); + + b.HasOne("RMuseum.Models.Artifact.RArtifactItemRecord", null) + .WithMany("Tags") + .HasForeignKey("RArtifactItemRecordId"); + + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", null) + .WithMany("Tags") + .HasForeignKey("RArtifactMasterRecordId"); + + b.HasOne("RMuseum.Models.Artifact.RTag", "RTag") + .WithMany() + .HasForeignKey("RTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RTag"); + }); + + modelBuilder.Entity("RMuseum.Models.Bookmark.RUserBookmark", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "RAppUser") + .WithMany() + .HasForeignKey("RAppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.Artifact.RArtifactItemRecord", "RArtifactItemRecord") + .WithMany() + .HasForeignKey("RArtifactItemRecordId"); + + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", "RArtifactMasterRecord") + .WithMany() + .HasForeignKey("RArtifactMasterRecordId"); + + b.Navigation("RAppUser"); + + b.Navigation("RArtifactItemRecord"); + + b.Navigation("RArtifactMasterRecord"); + }); + + modelBuilder.Entity("RMuseum.Models.FAQ.FAQItem", b => + { + b.HasOne("RMuseum.Models.FAQ.FAQCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCachedRelatedPoem", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poem"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCachedRelatedSection", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poem"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCat", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "Parent") + .WithMany() + .HasForeignKey("ParentId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Image.RImage", "RImage") + .WithMany() + .HasForeignKey("RImageId"); + + b.Navigation("Parent"); + + b.Navigation("Poet"); + + b.Navigation("RImage"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCatCorrection", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "Cat") + .WithMany() + .HasForeignKey("CatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cat"); + + b.Navigation("ReviewerUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCenturyPoet", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCentury", null) + .WithMany("Poets") + .HasForeignKey("GanjoorCenturyId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId"); + + b.Navigation("Poet"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorComment", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorComment", "InReplyTo") + .WithMany() + .HasForeignKey("InReplyToId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorVerse", "Verse1") + .WithMany() + .HasForeignKey("Verse1Id"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorVerse", "Verse2") + .WithMany() + .HasForeignKey("Verse2Id"); + + b.Navigation("InReplyTo"); + + b.Navigation("Poem"); + + b.Navigation("User"); + + b.Navigation("Verse1"); + + b.Navigation("Verse2"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCommentAbuseReport", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorComment", "GanjoorComment") + .WithMany() + .HasForeignKey("GanjoorCommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReportedBy") + .WithMany() + .HasForeignKey("ReportedById"); + + b.Navigation("GanjoorComment"); + + b.Navigation("ReportedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCommentReaction", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorComment", "GanjoorComment") + .WithMany() + .HasForeignKey("GanjoorCommentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GanjoorComment"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorDuplicate", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "DestPoem") + .WithMany() + .HasForeignKey("DestPoemId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "SrcPoem") + .WithMany() + .HasForeignKey("SrcPoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DestPoem"); + + b.Navigation("SrcPoem"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorNumbering", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "EndCat") + .WithMany() + .HasForeignKey("EndCatId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "StartCat") + .WithMany() + .HasForeignKey("StartCatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EndCat"); + + b.Navigation("StartCat"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPage", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "Cat") + .WithMany() + .HasForeignKey("CatId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPage", "Parent") + .WithMany() + .HasForeignKey("ParentId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "SecondPoet") + .WithMany() + .HasForeignKey("SecondPoetId"); + + b.Navigation("Cat"); + + b.Navigation("Parent"); + + b.Navigation("Poem"); + + b.Navigation("Poet"); + + b.Navigation("SecondPoet"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPageSnapshot", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPage", "GanjoorPage") + .WithMany() + .HasForeignKey("GanjoorPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "MadeObsoleteByUser") + .WithMany() + .HasForeignKey("MadeObsoleteByUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GanjoorPage"); + + b.Navigation("MadeObsoleteByUser"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonAffiliation", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person1") + .WithMany() + .HasForeignKey("Person1Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person2") + .WithMany() + .HasForeignKey("Person2Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Person1"); + + b.Navigation("Person2"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person") + .WithMany() + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "SuggestedBirthLocation") + .WithMany() + .HasForeignKey("SuggestedBirthLocationId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "SuggestedDeathLocation") + .WithMany() + .HasForeignKey("SuggestedDeathLocationId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + + b.Navigation("ReviewerUser"); + + b.Navigation("SuggestedBirthLocation"); + + b.Navigation("SuggestedDeathLocation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelation", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person1") + .WithMany() + .HasForeignKey("Person1Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person2") + .WithMany() + .HasForeignKey("Person2Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Person1"); + + b.Navigation("Person2"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPersonRelation", "ExistingRelation") + .WithMany() + .HasForeignKey("ExistingRelationId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person1") + .WithMany() + .HasForeignKey("Person1Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person2") + .WithMany() + .HasForeignKey("Person2Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ExistingRelation"); + + b.Navigation("Person1"); + + b.Navigation("Person2"); + + b.Navigation("ReviewerUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoem", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "Cat") + .WithMany() + .HasForeignKey("CatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorMetre", "GanjoorMetre") + .WithMany() + .HasForeignKey("GanjoorMetreId"); + + b.Navigation("Cat"); + + b.Navigation("GanjoorMetre"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poem"); + + b.Navigation("ReviewerUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemGeoDateTagCorrection", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", null) + .WithMany("GeoDateTags") + .HasForeignKey("GanjoorPoemCorrectionId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "Location") + .WithMany() + .HasForeignKey("LocationId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person") + .WithMany() + .HasForeignKey("PersonId"); + + b.Navigation("Location"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemProbableMetre", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poem"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemSection", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorMetre", "GanjoorMetre") + .WithMany() + .HasForeignKey("GanjoorMetreId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId"); + + b.Navigation("GanjoorMetre"); + + b.Navigation("Poem"); + + b.Navigation("Poet"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemSectionCorrection", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoemSection", "Section") + .WithMany() + .HasForeignKey("SectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReviewerUser"); + + b.Navigation("Section"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoet", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "BirthLocation") + .WithMany() + .HasForeignKey("BirthLocationId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "DeathLocation") + .WithMany() + .HasForeignKey("DeathLocationId"); + + b.HasOne("RSecurityBackend.Models.Image.RImage", "RImage") + .WithMany() + .HasForeignKey("RImageId"); + + b.Navigation("BirthLocation"); + + b.Navigation("DeathLocation"); + + b.Navigation("RImage"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoetSuggestedPicture", b => + { + b.HasOne("RMuseum.Models.Artifact.RPictureFile", "Picture") + .WithMany() + .HasForeignKey("PictureId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById"); + + b.Navigation("Picture"); + + b.Navigation("Poet"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoetSuggestedSpecLine", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById"); + + b.Navigation("Poet"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorQuotedPoem", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById"); + + b.Navigation("Poem"); + + b.Navigation("ReviewerUser"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "BirthLocation") + .WithMany() + .HasForeignKey("BirthLocationId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "DeathLocation") + .WithMany() + .HasForeignKey("DeathLocationId"); + + b.Navigation("BirthLocation"); + + b.Navigation("DeathLocation"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorSiteBanner", b => + { + b.HasOne("RSecurityBackend.Models.Image.RImage", "RImage") + .WithMany() + .HasForeignKey("RImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RImage"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikCat", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorTajikPoet", "Poet") + .WithMany() + .HasForeignKey("PoetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poet"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorTajikPoem", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorTajikCat", "Cat") + .WithMany() + .HasForeignKey("CatId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cat"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorUserBookmark", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Poem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorUserPoemVisit", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Poem"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorVerse", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorLanguage", "Language") + .WithMany() + .HasForeignKey("LanguageId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Language"); + + b.Navigation("Poem"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorVerseNumber", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorNumbering", "Numbering") + .WithMany() + .HasForeignKey("NumberingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Numbering"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorVerseVOrderText", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", null) + .WithMany("VerseOrderText") + .HasForeignKey("GanjoorPoemCorrectionId"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.PoemGeoDateTag", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorGeoLocation", "Location") + .WithMany() + .HasForeignKey("LocationId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person") + .WithMany() + .HasForeignKey("PersonId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Location"); + + b.Navigation("Person"); + + b.Navigation("Poem"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.PoemMusicTrack", b => + { + b.HasOne("RMuseum.Models.MusicCatalogue.GanjoorTrack", "GanjoorTrack") + .WithMany() + .HasForeignKey("GanjoorTrackId"); + + b.HasOne("RMuseum.Models.MusicCatalogue.GolhaTrack", "GolhaTrack") + .WithMany() + .HasForeignKey("GolhaTrackId"); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPoem", "Poem") + .WithMany() + .HasForeignKey("PoemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.MusicCatalogue.GanjoorSinger", "Singer") + .WithMany() + .HasForeignKey("SingerId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById"); + + b.Navigation("GanjoorTrack"); + + b.Navigation("GolhaTrack"); + + b.Navigation("Poem"); + + b.Navigation("Singer"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.Recitation", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Reviewer") + .WithMany() + .HasForeignKey("ReviewerId"); + + b.Navigation("Owner"); + + b.Navigation("Reviewer"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationApprovedMistake", b => + { + b.HasOne("RMuseum.Models.GanjoorAudio.Recitation", "Recitation") + .WithMany() + .HasForeignKey("RecitationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Recitation"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationErrorReport", b => + { + b.HasOne("RMuseum.Models.GanjoorAudio.Recitation", "Recitation") + .WithMany() + .HasForeignKey("RecitationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Reporter") + .WithMany() + .HasForeignKey("ReporterId"); + + b.Navigation("Recitation"); + + b.Navigation("Reporter"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationPublishingTracker", b => + { + b.HasOne("RMuseum.Models.GanjoorAudio.Recitation", "PoemNarration") + .WithMany() + .HasForeignKey("PoemNarrationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PoemNarration"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.RecitationUserUpVote", b => + { + b.HasOne("RMuseum.Models.GanjoorAudio.Recitation", "Recitation") + .WithMany() + .HasForeignKey("RecitationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Recitation"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorAudio.UserRecitationProfile", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.GanjoorLink", b => + { + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", "Artifact") + .WithMany() + .HasForeignKey("ArtifactId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.Artifact.RArtifactItemRecord", "Item") + .WithMany() + .HasForeignKey("ItemId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Reviewer") + .WithMany() + .HasForeignKey("ReviewerId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Artifact"); + + b.Navigation("Item"); + + b.Navigation("Reviewer"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.PDFGanjoorLink", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Reviewer") + .WithMany() + .HasForeignKey("ReviewerId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Reviewer"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.GanjoorIntegration.PinterestLink", b => + { + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", "Artifact") + .WithMany() + .HasForeignKey("ArtifactId"); + + b.HasOne("RMuseum.Models.Artifact.RArtifactItemRecord", "Item") + .WithMany() + .HasForeignKey("ItemId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Reviewer") + .WithMany() + .HasForeignKey("ReviewerId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "SuggestedBy") + .WithMany() + .HasForeignKey("SuggestedById"); + + b.Navigation("Artifact"); + + b.Navigation("Item"); + + b.Navigation("Reviewer"); + + b.Navigation("SuggestedBy"); + }); + + modelBuilder.Entity("RMuseum.Models.ImportJob.ImportJob", b => + { + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", "Artifact") + .WithMany() + .HasForeignKey("ArtifactId"); + + b.Navigation("Artifact"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorAlbum", b => + { + b.HasOne("RMuseum.Models.MusicCatalogue.GanjoorSinger", "Singer") + .WithMany("Albums") + .HasForeignKey("SingerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Singer"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorSinger", b => + { + b.HasOne("RSecurityBackend.Models.Image.RImage", "RImage") + .WithMany() + .HasForeignKey("RImageId"); + + b.Navigation("RImage"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorTrack", b => + { + b.HasOne("RMuseum.Models.MusicCatalogue.GanjoorAlbum", "Album") + .WithMany("Tracks") + .HasForeignKey("AlbumId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Album"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaProgram", b => + { + b.HasOne("RMuseum.Models.MusicCatalogue.GolhaCollection", "GolhaCollection") + .WithMany("Programs") + .HasForeignKey("GolhaCollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GolhaCollection"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaTrack", b => + { + b.HasOne("RMuseum.Models.MusicCatalogue.GolhaProgram", "GolhaProgram") + .WithMany("Tracks") + .HasForeignKey("GolhaProgramId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.MusicCatalogue.GanjoorSinger", "Singer") + .WithMany() + .HasForeignKey("SingerId"); + + b.Navigation("GolhaProgram"); + + b.Navigation("Singer"); + }); + + modelBuilder.Entity("RMuseum.Models.Note.RUserNote", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "RAppUser") + .WithMany() + .HasForeignKey("RAppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RMuseum.Models.Artifact.RArtifactItemRecord", "RArtifactItemRecord") + .WithMany() + .HasForeignKey("RArtifactItemRecordId"); + + b.HasOne("RMuseum.Models.Artifact.RArtifactMasterRecord", "RArtifactMasterRecord") + .WithMany() + .HasForeignKey("RArtifactMasterRecordId"); + + b.HasOne("RMuseum.Models.Note.RUserNote", "ReferenceNote") + .WithMany() + .HasForeignKey("ReferenceNoteId"); + + b.Navigation("RAppUser"); + + b.Navigation("RArtifactItemRecord"); + + b.Navigation("RArtifactMasterRecord"); + + b.Navigation("ReferenceNote"); + }); + + modelBuilder.Entity("RMuseum.Models.Note.RUserNoteAbuseReport", b => + { + b.HasOne("RMuseum.Models.Note.RUserNote", "Note") + .WithMany() + .HasForeignKey("NoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "Reporter") + .WithMany() + .HasForeignKey("ReporterId"); + + b.Navigation("Note"); + + b.Navigation("Reporter"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.Author", b => + { + b.HasOne("RSecurityBackend.Models.Image.RImage", "Image") + .WithMany() + .HasForeignKey("ImageId"); + + b.Navigation("Image"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.AuthorRole", b => + { + b.HasOne("RMuseum.Models.PDFLibrary.Author", "Author") + .WithMany() + .HasForeignKey("AuthorId"); + + b.HasOne("RMuseum.Models.PDFLibrary.Book", null) + .WithMany("Authors") + .HasForeignKey("BookId"); + + b.HasOne("RMuseum.Models.PDFLibrary.PDFBook", null) + .WithMany("Contributers") + .HasForeignKey("PDFBookId"); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.Book", b => + { + b.HasOne("RSecurityBackend.Models.Image.RImage", "CoverImage") + .WithMany() + .HasForeignKey("CoverImageId"); + + b.Navigation("CoverImage"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.MultiVolumePDFCollection", b => + { + b.HasOne("RMuseum.Models.PDFLibrary.Book", "Book") + .WithMany() + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Book"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFBook", b => + { + b.HasOne("RMuseum.Models.PDFLibrary.Book", "Book") + .WithMany() + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Image.RImage", "CoverImage") + .WithMany() + .HasForeignKey("CoverImageId"); + + b.HasOne("RMuseum.Models.PDFLibrary.MultiVolumePDFCollection", "MultiVolumePDFCollection") + .WithMany() + .HasForeignKey("MultiVolumePDFCollectionId"); + + b.HasOne("RSecurityBackend.Models.Image.RImage", "PDFFile") + .WithMany() + .HasForeignKey("PDFFileId"); + + b.HasOne("RMuseum.Models.PDFLibrary.PDFSource", "PDFSource") + .WithMany() + .HasForeignKey("PDFSourceId"); + + b.Navigation("Book"); + + b.Navigation("CoverImage"); + + b.Navigation("MultiVolumePDFCollection"); + + b.Navigation("PDFFile"); + + b.Navigation("PDFSource"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFPage", b => + { + b.HasOne("RMuseum.Models.PDFLibrary.PDFBook", null) + .WithMany("Pages") + .HasForeignKey("PDFBookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Image.RImage", "ThumbnailImage") + .WithMany() + .HasForeignKey("ThumbnailImageId"); + + b.Navigation("ThumbnailImage"); + }); + + modelBuilder.Entity("RMuseum.Models.UploadSession.UploadSession", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.UploadSession.UploadSessionFile", b => + { + b.HasOne("RMuseum.Models.UploadSession.UploadSession", null) + .WithMany("UploadedFiles") + .HasForeignKey("UploadSessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RAppUser", b => + { + b.HasOne("RSecurityBackend.Models.Image.RImage", "RImage") + .WithMany() + .HasForeignKey("RImageId"); + + b.Navigation("RImage"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RCaptchaImage", b => + { + b.HasOne("RSecurityBackend.Models.Image.RImage", "RImage") + .WithMany() + .HasForeignKey("RImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RImage"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RPermission", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppRole", null) + .WithMany("Permissions") + .HasForeignKey("RAppRoleId"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RTemporaryUserSession", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "RAppUser") + .WithMany() + .HasForeignKey("RAppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RAppUser"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RUserBehaviourLog", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.UserOldContact", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.ChangeTracking.RChangeLog", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "RAppUser") + .WithMany() + .HasForeignKey("RAppUserId"); + + b.HasOne("RSecurityBackend.Models.Cloud.RWorkspace", "Workspace") + .WithMany() + .HasForeignKey("WorkspaceId"); + + b.Navigation("RAppUser"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSPermission", b => + { + b.HasOne("RSecurityBackend.Models.Cloud.RWSRole", null) + .WithMany("Permissions") + .HasForeignKey("RWSRoleId"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSRole", b => + { + b.HasOne("RSecurityBackend.Models.Cloud.RWorkspace", "Workspace") + .WithMany() + .HasForeignKey("WorkspaceId"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSUser", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "RAppUser") + .WithMany() + .HasForeignKey("RAppUserId"); + + b.HasOne("RSecurityBackend.Models.Cloud.RWorkspace", null) + .WithMany("Members") + .HasForeignKey("RWorkspaceId"); + + b.Navigation("RAppUser"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSUserRole", b => + { + b.HasOne("RSecurityBackend.Models.Cloud.RWSRole", "Role") + .WithMany() + .HasForeignKey("RoleId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.HasOne("RSecurityBackend.Models.Cloud.RWorkspace", "Workspace") + .WithMany() + .HasForeignKey("WorkspaceId"); + + b.Navigation("Role"); + + b.Navigation("User"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.WorkspaceUserInvitation", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Cloud.RWorkspace", "Workspace") + .WithMany() + .HasForeignKey("WorkspaceId"); + + b.Navigation("User"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Generic.Db.RGenericOption", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "RAppUser") + .WithMany() + .HasForeignKey("RAppUserId"); + + b.HasOne("RSecurityBackend.Models.Cloud.RWorkspace", "Workspace") + .WithMany() + .HasForeignKey("WorkspaceId"); + + b.Navigation("RAppUser"); + + b.Navigation("Workspace"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Notification.RUserNotification", b => + { + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RPictureFile", b => + { + b.HasOne("RMuseum.Models.Artifact.RArtifactItemRecord", null) + .WithMany("Images") + .HasForeignKey("RArtifactItemRecordId"); + }); + + modelBuilder.Entity("RMuseum.Models.Accounting.GanjoorExpense", b => + { + b.Navigation("DonationExpenditures"); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RArtifactItemRecord", b => + { + b.Navigation("Images"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("RMuseum.Models.Artifact.RArtifactMasterRecord", b => + { + b.Navigation("Items"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorCentury", b => + { + b.Navigation("Poets"); + }); + + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoemCorrection", b => + { + b.Navigation("GeoDateTags"); + + b.Navigation("VerseOrderText"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorAlbum", b => + { + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GanjoorSinger", b => + { + b.Navigation("Albums"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaCollection", b => + { + b.Navigation("Programs"); + }); + + modelBuilder.Entity("RMuseum.Models.MusicCatalogue.GolhaProgram", b => + { + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.Book", b => + { + b.Navigation("Authors"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFBook", b => + { + b.Navigation("Contributers"); + + b.Navigation("Pages"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("RMuseum.Models.PDFLibrary.PDFPage", b => + { + b.Navigation("Tags"); + }); + + modelBuilder.Entity("RMuseum.Models.UploadSession.UploadSession", b => + { + b.Navigation("UploadedFiles"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Auth.Db.RAppRole", b => + { + b.Navigation("Permissions"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWSRole", b => + { + b.Navigation("Permissions"); + }); + + modelBuilder.Entity("RSecurityBackend.Models.Cloud.RWorkspace", b => + { + b.Navigation("Members"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.cs b/RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.cs new file mode 100644 index 00000000..66460839 --- /dev/null +++ b/RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.cs @@ -0,0 +1,114 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RMuseum.Migrations +{ + /// + public partial class AddGanjoorPersonRelationEditSuggestion : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SuggestedForDeletion", + table: "GanjoorPersonEditSuggestions", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "GanjoorPersonRelationEditSuggestions", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Action = table.Column(type: "int", nullable: false), + ExistingRelationId = table.Column(type: "int", nullable: true), + Person1Id = table.Column(type: "int", nullable: false), + Person2Id = table.Column(type: "int", nullable: false), + SuggestedRelationType = table.Column(type: "int", nullable: false), + SuggestedDegreeHint = table.Column(type: "int", nullable: true), + SuggestedNote = table.Column(type: "nvarchar(max)", nullable: true), + SuggestionNote = table.Column(type: "nvarchar(max)", nullable: true), + Date = table.Column(type: "datetime2", nullable: false), + UserId = table.Column(type: "uniqueidentifier", nullable: false), + Reviewed = table.Column(type: "bit", nullable: false), + Result = table.Column(type: "int", nullable: false), + ReviewDate = table.Column(type: "datetime2", nullable: false), + ReviewNote = table.Column(type: "nvarchar(max)", nullable: true), + ReviewerUserId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GanjoorPersonRelationEditSuggestions", x => x.Id); + table.ForeignKey( + name: "FK_GanjoorPersonRelationEditSuggestions_AspNetUsers_ReviewerUserId", + column: x => x.ReviewerUserId, + principalTable: "AspNetUsers", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_GanjoorPersonRelationEditSuggestions_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GanjoorPersonRelationEditSuggestions_GanjoorPersonRelations_ExistingRelationId", + column: x => x.ExistingRelationId, + principalTable: "GanjoorPersonRelations", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_GanjoorPersonRelationEditSuggestions_GanjoorRelatedPersons_Person1Id", + column: x => x.Person1Id, + principalTable: "GanjoorRelatedPersons", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_GanjoorPersonRelationEditSuggestions_GanjoorRelatedPersons_Person2Id", + column: x => x.Person2Id, + principalTable: "GanjoorRelatedPersons", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_GanjoorPersonRelationEditSuggestions_ExistingRelationId", + table: "GanjoorPersonRelationEditSuggestions", + column: "ExistingRelationId"); + + migrationBuilder.CreateIndex( + name: "IX_GanjoorPersonRelationEditSuggestions_Person1Id", + table: "GanjoorPersonRelationEditSuggestions", + column: "Person1Id"); + + migrationBuilder.CreateIndex( + name: "IX_GanjoorPersonRelationEditSuggestions_Person2Id", + table: "GanjoorPersonRelationEditSuggestions", + column: "Person2Id"); + + migrationBuilder.CreateIndex( + name: "IX_GanjoorPersonRelationEditSuggestions_ReviewerUserId", + table: "GanjoorPersonRelationEditSuggestions", + column: "ReviewerUserId"); + + migrationBuilder.CreateIndex( + name: "IX_GanjoorPersonRelationEditSuggestions_UserId", + table: "GanjoorPersonRelationEditSuggestions", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "GanjoorPersonRelationEditSuggestions"); + + migrationBuilder.DropColumn( + name: "SuggestedForDeletion", + table: "GanjoorPersonEditSuggestions"); + } + } +} diff --git a/RMuseum/Migrations/RMuseumDbContextModelSnapshot.cs b/RMuseum/Migrations/RMuseumDbContextModelSnapshot.cs index 9a150515..6ca829d0 100644 --- a/RMuseum/Migrations/RMuseumDbContextModelSnapshot.cs +++ b/RMuseum/Migrations/RMuseumDbContextModelSnapshot.cs @@ -1475,6 +1475,9 @@ namespace RMuseum.Migrations b.Property("SuggestedFamilyTreeCaption") .HasColumnType("nvarchar(max)"); + b.Property("SuggestedForDeletion") + .HasColumnType("bit"); + b.Property("SuggestedName") .HasColumnType("nvarchar(max)"); @@ -1540,6 +1543,74 @@ namespace RMuseum.Migrations b.ToTable("GanjoorPersonRelations"); }); + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("ExistingRelationId") + .HasColumnType("int"); + + b.Property("Person1Id") + .HasColumnType("int"); + + b.Property("Person2Id") + .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("SuggestedDegreeHint") + .HasColumnType("int"); + + b.Property("SuggestedNote") + .HasColumnType("nvarchar(max)"); + + b.Property("SuggestedRelationType") + .HasColumnType("int"); + + b.Property("SuggestionNote") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ExistingRelationId"); + + b.HasIndex("Person1Id"); + + b.HasIndex("Person2Id"); + + b.HasIndex("ReviewerUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GanjoorPersonRelationEditSuggestions"); + }); + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoem", b => { b.Property("Id") @@ -5441,6 +5512,46 @@ namespace RMuseum.Migrations b.Navigation("Person2"); }); + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion", b => + { + b.HasOne("RMuseum.Models.Ganjoor.GanjoorPersonRelation", "ExistingRelation") + .WithMany() + .HasForeignKey("ExistingRelationId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person1") + .WithMany() + .HasForeignKey("Person1Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("RMuseum.Models.Ganjoor.GanjoorRelatedPerson", "Person2") + .WithMany() + .HasForeignKey("Person2Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "ReviewerUser") + .WithMany() + .HasForeignKey("ReviewerUserId"); + + b.HasOne("RSecurityBackend.Models.Auth.Db.RAppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ExistingRelation"); + + b.Navigation("Person1"); + + b.Navigation("Person2"); + + b.Navigation("ReviewerUser"); + + b.Navigation("User"); + }); + modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPoem", b => { b.HasOne("RMuseum.Models.Ganjoor.GanjoorCat", "Cat") diff --git a/RMuseum/Models/Ganjoor/GanjoorPersonEditSuggestion.cs b/RMuseum/Models/Ganjoor/GanjoorPersonEditSuggestion.cs index fd444d5a..8f7e0502 100644 --- a/RMuseum/Models/Ganjoor/GanjoorPersonEditSuggestion.cs +++ b/RMuseum/Models/Ganjoor/GanjoorPersonEditSuggestion.cs @@ -108,6 +108,16 @@ namespace RMuseum.Models.Ganjoor /// public string SuggestedFamilyTreeCaption { get; set; } + /// + /// true if this suggestion is actually a request to delete the person outright, not to + /// change their fields - when true, every Suggested* field above is ignored on approval + /// (they still carry whatever the contributor's pre-filled form happened to hold) and + /// ModeratePersonEditSuggestionAsync removes the person and every relation/affiliation/tag + /// reference to them instead. Kept on this same entity rather than a separate one so the + /// existing suggest/review UI and permissions are reused as-is. + /// + public bool SuggestedForDeletion { get; set; } + /// /// suggester's note to the moderator /// diff --git a/RMuseum/Models/Ganjoor/GanjoorPersonRelationEditSuggestion.cs b/RMuseum/Models/Ganjoor/GanjoorPersonRelationEditSuggestion.cs new file mode 100644 index 00000000..5c42d554 --- /dev/null +++ b/RMuseum/Models/Ganjoor/GanjoorPersonRelationEditSuggestion.cs @@ -0,0 +1,128 @@ +using RSecurityBackend.Models.Auth.Db; +using System; + +namespace RMuseum.Models.Ganjoor +{ + /// + /// a contributor's suggestion to add a brand new kinship edge between two already-approved + /// people, change an existing edge's type/degree/note, or remove an existing edge outright - + /// the moderated counterpart of GanjoorPersonEditSuggestion, but for the edges between people + /// rather than one person's own fields. Reviewed the same way (see + /// GanjoorRelatedPersonService.ModeratePersonRelationEditSuggestionAsync). + /// + public class GanjoorPersonRelationEditSuggestion + { + /// + /// suggestion id + /// + public int Id { get; set; } + + /// + /// what this suggestion proposes doing + /// + public PersonRelationSuggestionAction Action { get; set; } + + /// + /// for Modify/Remove: the existing GanjoorPersonRelation row this suggestion targets. Null + /// for Add, where there's nothing existing yet. + /// + public int? ExistingRelationId { get; set; } + + /// + /// the existing relation being modified/removed (for showing a before/after diff to the + /// moderator) - null for Add + /// + public virtual GanjoorPersonRelation ExistingRelation { get; set; } + + /// + /// the pair this suggestion is about. For Add, this and Person2Id define the new relation. + /// For Modify/Remove, these are copied from ExistingRelation at submission time, so the + /// suggestion always accurately reflects what it targets even if the underlying relation + /// changes again before this is reviewed. + /// + public int Person1Id { get; set; } + + /// + /// first person (navigation) + /// + public virtual GanjoorRelatedPerson Person1 { get; set; } + + /// + /// second person id, see Person1Id + /// + public int Person2Id { get; set; } + + /// + /// second person (navigation) + /// + public virtual GanjoorRelatedPerson Person2 { get; set; } + + /// + /// for Add/Modify: the relation type to create/change to. For Remove: a snapshot of the + /// existing relation's type at submission time, purely for display (removing doesn't use it). + /// + public PersonRelationType SuggestedRelationType { get; set; } + + /// + /// see GanjoorPersonRelation.DegreeHint - same for/Add/Modify/Remove-display purpose as + /// SuggestedRelationType above + /// + public int? SuggestedDegreeHint { get; set; } + + /// + /// see GanjoorPersonRelation.Note - same Add/Modify/Remove-display purpose as + /// SuggestedRelationType above + /// + public string SuggestedNote { get; set; } + + /// + /// suggester's note to the moderator (e.g. why an existing relation is wrong) + /// + public string SuggestionNote { get; set; } + + /// + /// date + /// + public DateTime Date { get; set; } + + /// + /// suggester's user id + /// + public Guid UserId { get; set; } + + /// + /// suggester + /// + public virtual RAppUser User { get; set; } + + /// + /// reviewed + /// + public bool Reviewed { get; set; } + + /// + /// review result + /// + public CorrectionReviewResult Result { get; set; } + + /// + /// review date + /// + public DateTime ReviewDate { get; set; } + + /// + /// review note + /// + public string ReviewNote { get; set; } + + /// + /// reviewer's user id + /// + public Guid? ReviewerUserId { get; set; } + + /// + /// reviewer + /// + public virtual RAppUser ReviewerUser { get; set; } + } +} diff --git a/RMuseum/Models/Ganjoor/PersonRelationSuggestionAction.cs b/RMuseum/Models/Ganjoor/PersonRelationSuggestionAction.cs new file mode 100644 index 00000000..384725af --- /dev/null +++ b/RMuseum/Models/Ganjoor/PersonRelationSuggestionAction.cs @@ -0,0 +1,23 @@ +namespace RMuseum.Models.Ganjoor +{ + /// + /// what a GanjoorPersonRelationEditSuggestion proposes doing to the kinship graph + /// + public enum PersonRelationSuggestionAction + { + /// + /// create a brand new GanjoorPersonRelation between Person1Id and Person2Id + /// + Add = 0, + + /// + /// change the RelationType/DegreeHint/Note of the existing relation ExistingRelationId points to + /// + Modify = 1, + + /// + /// remove the existing relation ExistingRelationId points to outright + /// + Remove = 2, + } +} diff --git a/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs b/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs index a8d0a24d..b821755a 100644 --- a/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs +++ b/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs @@ -30,6 +30,12 @@ namespace RMuseum.Models.Ganjoor.ViewModels /// public class GanjoorPersonRelationInfo { + /// + /// the underlying GanjoorPersonRelation row's own id - needed by the client to link to + /// /SuggestPersonRelationEdit/{id} for suggesting a change/removal of this specific edge + /// + public int Id { get; set; } + /// /// the other person in this relation /// diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml index 69a8c713..11955906 100644 --- a/RMuseum/RMuseum.xml +++ b/RMuseum/RMuseum.xml @@ -2894,6 +2894,39 @@ + + + get a single kinship edge by its own id, with both sides' names resolved - used by + /SuggestPersonRelationEdit/{relationId} to show what it's about + + + + + + + suggest an addition, change or removal of a kinship edge - any logged-in user, same as + suggesting a person edit. Goes into the pending queue; does not change anything. + + + + + + + get the next unreviewed relation-edit suggestion, for the moderator queue - same + permission as reviewing a person edit + + + + + + + apply a moderator's decision to a pending relation-edit suggestion - same permission as + moderating a person edit + + + + + person service @@ -4030,6 +4063,12 @@ see GanjoorPersonEditSuggestion + + + pending/reviewed suggested additions, changes or removals of a kinship edge between two + already-approved people - see GanjoorPersonRelationEditSuggestion + + Books (PDF Library) @@ -4877,6 +4916,18 @@ + + + + + + + + + + + + donation expenditures @@ -9396,6 +9447,16 @@ suggested family tree caption + + + true if this suggestion is actually a request to delete the person outright, not to + change their fields - when true, every Suggested* field above is ignored on approval + (they still carry whatever the contributor's pre-filled form happened to hold) and + ModeratePersonEditSuggestionAsync removes the person and every relation/affiliation/tag + reference to them instead. Kept on this same entity rather than a separate one so the + existing suggest/review UI and permissions are reused as-is. + + suggester's note to the moderator @@ -9483,6 +9544,128 @@ free-text note (e.g. sourcing/reasoning for this relation) + + + a contributor's suggestion to add a brand new kinship edge between two already-approved + people, change an existing edge's type/degree/note, or remove an existing edge outright - + the moderated counterpart of GanjoorPersonEditSuggestion, but for the edges between people + rather than one person's own fields. Reviewed the same way (see + GanjoorRelatedPersonService.ModeratePersonRelationEditSuggestionAsync). + + + + + suggestion id + + + + + what this suggestion proposes doing + + + + + for Modify/Remove: the existing GanjoorPersonRelation row this suggestion targets. Null + for Add, where there's nothing existing yet. + + + + + the existing relation being modified/removed (for showing a before/after diff to the + moderator) - null for Add + + + + + the pair this suggestion is about. For Add, this and Person2Id define the new relation. + For Modify/Remove, these are copied from ExistingRelation at submission time, so the + suggestion always accurately reflects what it targets even if the underlying relation + changes again before this is reviewed. + + + + + first person (navigation) + + + + + second person id, see Person1Id + + + + + second person (navigation) + + + + + for Add/Modify: the relation type to create/change to. For Remove: a snapshot of the + existing relation's type at submission time, purely for display (removing doesn't use it). + + + + + see GanjoorPersonRelation.DegreeHint - same for/Add/Modify/Remove-display purpose as + SuggestedRelationType above + + + + + see GanjoorPersonRelation.Note - same Add/Modify/Remove-display purpose as + SuggestedRelationType above + + + + + suggester's note to the moderator (e.g. why an existing relation is wrong) + + + + + date + + + + + suggester's user id + + + + + suggester + + + + + reviewed + + + + + review result + + + + + review date + + + + + review note + + + + + reviewer's user id + + + + + reviewer + + Ganjoor Poem @@ -11571,6 +11754,26 @@ doesn't fit any of the above - rely on Note for what the tie actually is + + + what a GanjoorPersonRelationEditSuggestion proposes doing to the kinship graph + + + + + create a brand new GanjoorPersonRelation between Person1Id and Person2Id + + + + + change the RelationType/DegreeHint/Note of the existing relation ExistingRelationId points to + + + + + remove the existing relation ExistingRelationId points to outright + + the kind of kinship edge a GanjoorPersonRelation represents between two GanjoorRelatedPerson @@ -13116,6 +13319,12 @@ one GanjoorPersonRelation row, resolved from Person's point of view + + + the underlying GanjoorPersonRelation row's own id - needed by the client to link to + /SuggestPersonRelationEdit/{id} for suggesting a change/removal of this specific edge + + the other person in this relation @@ -18008,6 +18217,48 @@ + + + get a single kinship edge by its own id, with both sides' names resolved - used by + /SuggestPersonRelationEdit/{relationId} to show what it's about + + + + + + + submit a suggested addition, change or removal of a kinship edge - goes into the + pending queue, does not change anything until a moderator approves it + + + + + + + get the next unreviewed relation-edit suggestion (for the moderator queue) + + + + + + + unreviewed relation-edit suggestion count + + + + + + apply a moderator's decision to a pending relation-edit suggestion. On Approved: Add + creates a new GanjoorPersonRelation, Modify updates the existing one ExistingRelationId + points to, Remove deletes it (and auto-rejects any other still-pending suggestion that + also targeted that same now-gone relation, so it doesn't dangle). + + + + + + + Ganjoor Poems Content Privider Service @@ -21514,16 +21765,82 @@ + + + removes a person and every row that would otherwise block that deletion at the database + level (kinship edges, affiliations, and other pending relation-edit suggestions touching + them - all Restrict-on-delete FKs, see RMuseumDbContext.OnModelCreating), plus detaches + (but does not delete) any approved poem geo/date tag that named them, since the tag + itself may still carry a real location/date worth keeping. + + + + + get a single kinship edge by its own id, with both sides' names resolved + + + + + + + submit a suggested addition, change or removal of a kinship edge + + + + + + + get the next unreviewed relation-edit suggestion for the moderator queue + + + + + + + unreviewed relation-edit suggestion count + + + + + + apply a moderator's decision to a pending relation-edit suggestion + + + + + + + Database Context - + + + used to find every user holding the Ganjoor:Modify permission, to notify them when a new + suggestion needs review - same permission the moderation endpoints themselves require + + + + + used to notify moderators of a new pending suggestion, and submitters of its outcome - + same service/pattern GanjoorService uses for poem corrections and song suggestions + + + constructor + + + + + + notify every user holding the Ganjoor:Modify permission that a new suggestion of the + given kind is pending review at reviewPageUrl - shared by both suggestion types below + diff --git a/RMuseum/Services/IGanjoorRelatedPersonService.cs b/RMuseum/Services/IGanjoorRelatedPersonService.cs index 105a5134..b42df220 100644 --- a/RMuseum/Services/IGanjoorRelatedPersonService.cs +++ b/RMuseum/Services/IGanjoorRelatedPersonService.cs @@ -96,5 +96,47 @@ namespace RMuseum.Services /// /// Task> ModeratePersonEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote); + + /// + /// get a single kinship edge by its own id, with both sides' names resolved - used by + /// /SuggestPersonRelationEdit/{relationId} to show what it's about + /// + /// + /// + Task> GetRelationByIdAsync(int relationId); + + /// + /// submit a suggested addition, change or removal of a kinship edge - goes into the + /// pending queue, does not change anything until a moderator approves it + /// + /// + /// + Task> SuggestPersonRelationEditAsync(GanjoorPersonRelationEditSuggestion suggestion); + + /// + /// get the next unreviewed relation-edit suggestion (for the moderator queue) + /// + /// + /// + Task> GetNextUnreviewedPersonRelationEditSuggestionAsync(int skip); + + /// + /// unreviewed relation-edit suggestion count + /// + /// + Task> GetUnreviewedPersonRelationEditSuggestionCountAsync(); + + /// + /// apply a moderator's decision to a pending relation-edit suggestion. On Approved: Add + /// creates a new GanjoorPersonRelation, Modify updates the existing one ExistingRelationId + /// points to, Remove deletes it (and auto-rejects any other still-pending suggestion that + /// also targeted that same now-gone relation, so it doesn't dangle). + /// + /// + /// + /// + /// + /// + Task> ModeratePersonRelationEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote); } } diff --git a/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs b/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs index 13813a35..ee0367be 100644 --- a/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs +++ b/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs @@ -1,8 +1,12 @@ using Microsoft.EntityFrameworkCore; using RMuseum.DbContext; +using RMuseum.Models.Auth.Memory; using RMuseum.Models.Ganjoor; using RMuseum.Models.Ganjoor.ViewModels; +using RSecurityBackend.Models.Auth.Memory; using RSecurityBackend.Models.Generic; +using RSecurityBackend.Models.Notification; +using RSecurityBackend.Services; using System; using System.Collections.Generic; using System.Linq; @@ -102,6 +106,7 @@ namespace RMuseum.Services.Implementation var relations = relationRows.Select(r => new GanjoorPersonRelationInfo() { + Id = r.Id, OtherPersonId = r.Person1Id == id ? r.Person2Id : r.Person1Id, OtherPersonName = r.Person1Id == id ? r.Person2.Name : r.Person1.Name, RelationType = r.RelationType, @@ -268,20 +273,20 @@ namespace RMuseum.Services.Implementation { try { - if (suggestion == null || string.IsNullOrWhiteSpace(suggestion.SuggestedName)) + if (suggestion == null || (!suggestion.SuggestedForDeletion && string.IsNullOrWhiteSpace(suggestion.SuggestedName))) { return new RServiceResult(null, "نام شخصیت نمی‌تواند خالی باشد."); } - var personExists = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).AnyAsync(); - if (!personExists) + var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).SingleOrDefaultAsync(); + if (person == null) { return new RServiceResult(null, "شخصیت پیدا نشد."); } suggestion.Id = 0; suggestion.Date = DateTime.Now; - suggestion.SuggestedName = suggestion.SuggestedName.Trim(); + suggestion.SuggestedName = suggestion.SuggestedName?.Trim(); suggestion.SuggestedDescription = string.IsNullOrWhiteSpace(suggestion.SuggestedDescription) ? null : suggestion.SuggestedDescription.Trim(); suggestion.SuggestedWikiUrl = string.IsNullOrWhiteSpace(suggestion.SuggestedWikiUrl) ? null : suggestion.SuggestedWikiUrl.Trim(); suggestion.SuggestedFamilyTreeCaption = string.IsNullOrWhiteSpace(suggestion.SuggestedFamilyTreeCaption) ? null : suggestion.SuggestedFamilyTreeCaption.Trim(); @@ -293,6 +298,14 @@ namespace RMuseum.Services.Implementation _context.GanjoorPersonEditSuggestions.Add(suggestion); await _context.SaveChangesAsync(); + await NotifyModeratorsOfPendingSuggestionAsync( + suggestion.SuggestedForDeletion ? "پیشنهاد حذف شخصیت" : "پیشنهاد ویرایش شخصیت", + (suggestion.SuggestedForDeletion + ? $"کاربری پیشنهاد حذف شخصیت «{person.Name}» را داده است." + : $"کاربری ویرایش جدیدی برای شخصیت «{person.Name}» پیشنهاد داده است.") + + $" لطفاً بخش ویرایش‌های پیشنهادی شخصیت‌ها را بررسی فرمایید." + ); + return new RServiceResult(suggestion); } catch (Exception exp) @@ -367,25 +380,33 @@ namespace RMuseum.Services.Implementation return new RServiceResult(null, "این پیشنهاد پیش‌تر بررسی شده است."); } + var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).SingleOrDefaultAsync(); + if (person == null) + { + return new RServiceResult(null, "شخصیت مقصد این پیشنهاد پیدا نشد."); + } + var personName = person.Name; // snapshot before a possible deletion below, for the notification text + if (result == CorrectionReviewResult.Approved) { - var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).SingleOrDefaultAsync(); - if (person == null) + if (suggestion.SuggestedForDeletion) { - return new RServiceResult(null, "شخصیت مقصد این پیشنهاد پیدا نشد."); + await DeletePersonAndReferencesAsync(person); + } + else + { + person.Name = suggestion.SuggestedName; + person.Description = suggestion.SuggestedDescription; + person.WikiUrl = suggestion.SuggestedWikiUrl; + person.BirthYearInLHijri = suggestion.SuggestedBirthYearInLHijri; + person.DeathYearInLHijri = suggestion.SuggestedDeathYearInLHijri; + person.ValidBirthDate = suggestion.SuggestedValidBirthDate; + person.ValidDeathDate = suggestion.SuggestedValidDeathDate; + person.BirthLocationId = suggestion.SuggestedBirthLocationId; + person.DeathLocationId = suggestion.SuggestedDeathLocationId; + person.FamilyTreeCaption = suggestion.SuggestedFamilyTreeCaption; + // Id and MachineGenerated on the person are intentionally left untouched } - - person.Name = suggestion.SuggestedName; - person.Description = suggestion.SuggestedDescription; - person.WikiUrl = suggestion.SuggestedWikiUrl; - person.BirthYearInLHijri = suggestion.SuggestedBirthYearInLHijri; - person.DeathYearInLHijri = suggestion.SuggestedDeathYearInLHijri; - person.ValidBirthDate = suggestion.SuggestedValidBirthDate; - person.ValidDeathDate = suggestion.SuggestedValidDeathDate; - person.BirthLocationId = suggestion.SuggestedBirthLocationId; - person.DeathLocationId = suggestion.SuggestedDeathLocationId; - person.FamilyTreeCaption = suggestion.SuggestedFamilyTreeCaption; - // Id and MachineGenerated on the person are intentionally left untouched } suggestion.Reviewed = true; @@ -396,6 +417,29 @@ namespace RMuseum.Services.Implementation await _context.SaveChangesAsync(); + if (result == CorrectionReviewResult.Approved) + { + await _notificationService.PushNotification( + suggestion.UserId, + suggestion.SuggestedForDeletion ? "تأیید حذف شخصیت پیشنهادی" : "تأیید ویرایش پیشنهادی شخصیت", + suggestion.SuggestedForDeletion + ? $"پیشنهاد شما برای حذف شخصیت «{personName}» تأیید و اعمال شد. از این که به تکمیل اطلاعات گنجور کمک کردید سپاسگزاریم." + : $"ویرایش پیشنهادی شما برای شخصیت «{personName}» تأیید و اعمال شد. از این که به تکمیل اطلاعات گنجور کمک کردید سپاسگزاریم." + ); + } + else + { + await _notificationService.PushNotification( + suggestion.UserId, + suggestion.SuggestedForDeletion ? "رد پیشنهاد حذف شخصیت" : "رد ویرایش پیشنهادی شخصیت", + (suggestion.SuggestedForDeletion + ? $"پیشنهاد شما برای حذف شخصیت «{personName}» تأیید نشد." + : $"ویرایش پیشنهادی شما برای شخصیت «{personName}» تأیید نشد.") + + (string.IsNullOrWhiteSpace(reviewNote) ? "" : $"{Environment.NewLine}یادداشت بازبین: «{reviewNote}»"), + NotificationType.Warning + ); + } + return new RServiceResult(suggestion); } catch (Exception exp) @@ -404,18 +448,407 @@ namespace RMuseum.Services.Implementation } } + /// + /// removes a person and every row that would otherwise block that deletion at the database + /// level (kinship edges, affiliations, and other pending relation-edit suggestions touching + /// them - all Restrict-on-delete FKs, see RMuseumDbContext.OnModelCreating), plus detaches + /// (but does not delete) any approved poem geo/date tag that named them, since the tag + /// itself may still carry a real location/date worth keeping. + /// + private async Task DeletePersonAndReferencesAsync(GanjoorRelatedPerson person) + { + var relations = await _context.GanjoorPersonRelations + .Where(r => r.Person1Id == person.Id || r.Person2Id == person.Id) + .ToListAsync(); + var relationIds = relations.Select(r => r.Id).ToList(); + + var affiliations = await _context.GanjoorPersonAffiliations + .Where(a => a.Person1Id == person.Id || a.Person2Id == person.Id) + .ToListAsync(); + + // any other still-pending relation-edit suggestion that touches this person directly, or + // targets one of the relations we're about to remove, would otherwise dangle or violate + // the Restrict FK above - auto-reject those with an explanatory note rather than letting + // the delete fail or silently drop them + var conflictingRelationSuggestions = await _context.GanjoorPersonRelationEditSuggestions + .Where(s => !s.Reviewed && ( + s.Person1Id == person.Id || + s.Person2Id == person.Id || + (s.ExistingRelationId != null && relationIds.Contains(s.ExistingRelationId.Value)) + )) + .ToListAsync(); + foreach (var conflicting in conflictingRelationSuggestions) + { + conflicting.Reviewed = true; + conflicting.Result = CorrectionReviewResult.RejectedBecauseUnnecessaryChange; + conflicting.ReviewNote = "شخصیت یا نسبت مرتبط با این پیشنهاد حذف شد."; + conflicting.ReviewDate = DateTime.Now; + } + + var geoDateTags = await _context.PoemGeoDateTags.Where(t => t.PersonId == person.Id).ToListAsync(); + foreach (var tag in geoDateTags) + { + tag.PersonId = null; + } + + // any other still-pending edit suggestion FOR this same person (not the one being + // approved right now, which the caller updates separately) is moot once the person is + // gone - same auto-reject treatment + var conflictingEditSuggestions = await _context.GanjoorPersonEditSuggestions + .Where(s => !s.Reviewed && s.PersonId == person.Id) + .ToListAsync(); + foreach (var conflicting in conflictingEditSuggestions) + { + conflicting.Reviewed = true; + conflicting.Result = CorrectionReviewResult.RejectedBecauseUnnecessaryChange; + conflicting.ReviewNote = "این شخصیت حذف شد."; + conflicting.ReviewDate = DateTime.Now; + } + + _context.GanjoorPersonRelations.RemoveRange(relations); + _context.GanjoorPersonAffiliations.RemoveRange(affiliations); + _context.GanjoorRelatedPersons.Remove(person); + } + + /// + /// get a single kinship edge by its own id, with both sides' names resolved + /// + /// + /// + public async Task> GetRelationByIdAsync(int relationId) + { + try + { + var relation = await _context.GanjoorPersonRelations + .Include(r => r.Person1) + .Include(r => r.Person2) + .Where(r => r.Id == relationId) + .SingleOrDefaultAsync(); + + if (relation == null) + { + return new RServiceResult(null, "نسبت پیدا نشد."); + } + + return new RServiceResult(relation); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + + /// + /// submit a suggested addition, change or removal of a kinship edge + /// + /// + /// + public async Task> SuggestPersonRelationEditAsync(GanjoorPersonRelationEditSuggestion suggestion) + { + try + { + if (suggestion == null) + { + return new RServiceResult(null, "اطلاعات پیشنهاد ناقص است."); + } + + GanjoorPersonRelation existingRelation = null; + if (suggestion.Action == PersonRelationSuggestionAction.Modify || suggestion.Action == PersonRelationSuggestionAction.Remove) + { + if (suggestion.ExistingRelationId == null) + { + return new RServiceResult(null, "نسبت مورد نظر برای ویرایش یا حذف مشخص نشده است."); + } + + existingRelation = await _context.GanjoorPersonRelations + .Include(r => r.Person1) + .Include(r => r.Person2) + .Where(r => r.Id == suggestion.ExistingRelationId.Value) + .SingleOrDefaultAsync(); + + if (existingRelation == null) + { + return new RServiceResult(null, "نسبت مورد نظر پیدا نشد."); + } + + // always trust the existing relation's own Person1Id/Person2Id/type/etc over + // whatever the client sent, so the suggestion is guaranteed self-consistent with + // what it actually targets, even for a Remove-display + suggestion.Person1Id = existingRelation.Person1Id; + suggestion.Person2Id = existingRelation.Person2Id; + if (suggestion.Action == PersonRelationSuggestionAction.Remove) + { + suggestion.SuggestedRelationType = existingRelation.RelationType; + suggestion.SuggestedDegreeHint = existingRelation.DegreeHint; + suggestion.SuggestedNote = existingRelation.Note; + } + } + else // Add + { + if (suggestion.Person1Id == suggestion.Person2Id) + { + return new RServiceResult(null, "دو طرف یک نسبت نمی‌توانند یک نفر باشند."); + } + } + + var person1 = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.Person1Id).SingleOrDefaultAsync(); + var person2 = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.Person2Id).SingleOrDefaultAsync(); + if (person1 == null || person2 == null) + { + return new RServiceResult(null, "یکی از دو طرف نسبت پیدا نشد."); + } + + suggestion.Id = 0; + suggestion.ExistingRelation = null; + suggestion.Person1 = null; + suggestion.Person2 = null; + suggestion.Date = DateTime.Now; + suggestion.SuggestedNote = string.IsNullOrWhiteSpace(suggestion.SuggestedNote) ? null : suggestion.SuggestedNote.Trim(); + suggestion.SuggestionNote = string.IsNullOrWhiteSpace(suggestion.SuggestionNote) ? null : suggestion.SuggestionNote.Trim(); + suggestion.Reviewed = false; + suggestion.Result = CorrectionReviewResult.NotReviewed; + suggestion.ReviewNote = null; + suggestion.ReviewerUserId = null; + + _context.GanjoorPersonRelationEditSuggestions.Add(suggestion); + await _context.SaveChangesAsync(); + + string actionTitle = suggestion.Action switch + { + PersonRelationSuggestionAction.Add => "پیشنهاد نسبت خویشاوندی جدید", + PersonRelationSuggestionAction.Modify => "پیشنهاد ویرایش نسبت خویشاوندی", + _ => "پیشنهاد حذف نسبت خویشاوندی", + }; + string actionText = suggestion.Action switch + { + PersonRelationSuggestionAction.Add => $"کاربری پیشنهاد افزودن نسبت خویشاوندی جدید بین «{person1.Name}» و «{person2.Name}» را داده است.", + PersonRelationSuggestionAction.Modify => $"کاربری پیشنهاد ویرایش نسبت خویشاوندی بین «{person1.Name}» و «{person2.Name}» را داده است.", + _ => $"کاربری پیشنهاد حذف نسبت خویشاوندی بین «{person1.Name}» و «{person2.Name}» را داده است.", + }; + + await NotifyModeratorsOfPendingSuggestionAsync( + actionTitle, + actionText + " لطفاً بخش ویرایش‌های پیشنهادی نسبت‌های خویشاوندی را بررسی فرمایید." + ); + + return new RServiceResult(suggestion); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + + /// + /// get the next unreviewed relation-edit suggestion for the moderator queue + /// + /// + /// + public async Task> GetNextUnreviewedPersonRelationEditSuggestionAsync(int skip) + { + try + { + var suggestion = await _context.GanjoorPersonRelationEditSuggestions + .Include(s => s.Person1) + .Include(s => s.Person2) + .Include(s => s.ExistingRelation) + .Include(s => s.User) + .Where(s => s.Reviewed == false) + .OrderBy(s => s.Id) + .Skip(skip) + .FirstOrDefaultAsync(); + + return new RServiceResult(suggestion); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + + /// + /// unreviewed relation-edit suggestion count + /// + /// + public async Task> GetUnreviewedPersonRelationEditSuggestionCountAsync() + { + try + { + return new RServiceResult(await _context.GanjoorPersonRelationEditSuggestions.Where(s => s.Reviewed == false).CountAsync()); + } + catch (Exception exp) + { + return new RServiceResult(0, exp.ToString()); + } + } + + /// + /// apply a moderator's decision to a pending relation-edit suggestion + /// + /// + /// + /// + /// + /// + public async Task> ModeratePersonRelationEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote) + { + try + { + var suggestion = await _context.GanjoorPersonRelationEditSuggestions + .Include(s => s.Person1) + .Include(s => s.Person2) + .Where(s => s.Id == suggestionId) + .SingleOrDefaultAsync(); + + if (suggestion == null) + { + return new RServiceResult(null, "پیشنهاد پیدا نشد."); + } + + if (suggestion.Reviewed) + { + return new RServiceResult(null, "این پیشنهاد پیش‌تر بررسی شده است."); + } + + var person1Name = suggestion.Person1?.Name; + var person2Name = suggestion.Person2?.Name; + + if (result == CorrectionReviewResult.Approved) + { + switch (suggestion.Action) + { + case PersonRelationSuggestionAction.Add: + _context.GanjoorPersonRelations.Add(new GanjoorPersonRelation() + { + Person1Id = suggestion.Person1Id, + Person2Id = suggestion.Person2Id, + RelationType = suggestion.SuggestedRelationType, + DegreeHint = suggestion.SuggestedDegreeHint, + Note = suggestion.SuggestedNote, + }); + break; + case PersonRelationSuggestionAction.Modify: + { + var existing = await _context.GanjoorPersonRelations.Where(r => r.Id == suggestion.ExistingRelationId.Value).SingleOrDefaultAsync(); + if (existing == null) + { + return new RServiceResult(null, "نسبت مورد نظر دیگر وجود ندارد."); + } + existing.RelationType = suggestion.SuggestedRelationType; + existing.DegreeHint = suggestion.SuggestedDegreeHint; + existing.Note = suggestion.SuggestedNote; + break; + } + case PersonRelationSuggestionAction.Remove: + { + var existing = await _context.GanjoorPersonRelations.Where(r => r.Id == suggestion.ExistingRelationId.Value).SingleOrDefaultAsync(); + if (existing != null) + { + // any other still-pending suggestion targeting this same relation + // would otherwise dangle once it's gone - auto-reject those too + var conflicting = await _context.GanjoorPersonRelationEditSuggestions + .Where(s => !s.Reviewed && s.Id != suggestion.Id && s.ExistingRelationId == existing.Id) + .ToListAsync(); + foreach (var c in conflicting) + { + c.Reviewed = true; + c.Result = CorrectionReviewResult.RejectedBecauseUnnecessaryChange; + c.ReviewNote = "این نسبت پیش‌تر حذف شد."; + c.ReviewDate = DateTime.Now; + } + _context.GanjoorPersonRelations.Remove(existing); + } + break; + } + } + } + + suggestion.Reviewed = true; + suggestion.Result = result; + suggestion.ReviewNote = reviewNote; + suggestion.ReviewDate = DateTime.Now; + suggestion.ReviewerUserId = moderatorUserId; + + await _context.SaveChangesAsync(); + + string actionLabel = suggestion.Action switch + { + PersonRelationSuggestionAction.Add => "افزودن نسبت خویشاوندی", + PersonRelationSuggestionAction.Modify => "ویرایش نسبت خویشاوندی", + _ => "حذف نسبت خویشاوندی", + }; + + if (result == CorrectionReviewResult.Approved) + { + await _notificationService.PushNotification( + suggestion.UserId, + $"تأیید {actionLabel}", + $"پیشنهاد شما برای {actionLabel} بین «{person1Name}» و «{person2Name}» تأیید و اعمال شد. از این که به تکمیل اطلاعات گنجور کمک کردید سپاسگزاریم." + ); + } + else + { + await _notificationService.PushNotification( + suggestion.UserId, + $"رد {actionLabel}", + $"پیشنهاد شما برای {actionLabel} بین «{person1Name}» و «{person2Name}» تأیید نشد." + + (string.IsNullOrWhiteSpace(reviewNote) ? "" : $"{Environment.NewLine}یادداشت بازبین: «{reviewNote}»"), + NotificationType.Warning + ); + } + + return new RServiceResult(suggestion); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + /// /// Database Context /// protected readonly RMuseumDbContext _context; + /// + /// used to find every user holding the Ganjoor:Modify permission, to notify them when a new + /// suggestion needs review - same permission the moderation endpoints themselves require + /// + protected readonly IAppUserService _appUserService; + + /// + /// used to notify moderators of a new pending suggestion, and submitters of its outcome - + /// same service/pattern GanjoorService uses for poem corrections and song suggestions + /// + protected readonly IRNotificationService _notificationService; + /// /// constructor /// /// - public GanjoorRelatedPersonService(RMuseumDbContext context) + /// + /// + public GanjoorRelatedPersonService(RMuseumDbContext context, IAppUserService appUserService, IRNotificationService notificationService) { _context = context; + _appUserService = appUserService; + _notificationService = notificationService; + } + + /// + /// notify every user holding the Ganjoor:Modify permission that a new suggestion of the + /// given kind is pending review at reviewPageUrl - shared by both suggestion types below + /// + private async Task NotifyModeratorsOfPendingSuggestionAsync(string title, string htmlText) + { + var moderators = await _appUserService.GetUsersHavingPermission(RMuseumSecurableItem.GanjoorEntityShortName, SecurableItem.ModifyOperationShortName); + if (string.IsNullOrEmpty(moderators.ExceptionString)) + { + foreach (var moderator in moderators.Result) + { + await _notificationService.PushNotification((Guid)moderator.Id, title, htmlText, NotificationType.ActionRequired); + } + } } } }