people tags #387
This commit is contained in:
parent
9d12c23400
commit
bb68c074b0
@ -45,67 +45,77 @@ else
|
||||
}
|
||||
</table>
|
||||
|
||||
<table class="up-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>فیلد</th>
|
||||
<th>مقدار فعلی</th>
|
||||
<th>پیشنهاد</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>نام</td>
|
||||
<td>@p.Name</td>
|
||||
<td>@s.SuggestedName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>نشانی ویکیپدیا</td>
|
||||
<td>@p.WikiUrl</td>
|
||||
<td>@s.SuggestedWikiUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>سال تولد (قمری)</td>
|
||||
<td>@(p.BirthYearInLHijri == 0 ? "" : p.BirthYearInLHijri.ToPersianNumbers())</td>
|
||||
<td>@(s.SuggestedBirthYearInLHijri == 0 ? "" : s.SuggestedBirthYearInLHijri.ToPersianNumbers())</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>تاریخ تولد مستند است</td>
|
||||
<td>@(p.ValidBirthDate ? "بله" : "خیر")</td>
|
||||
<td>@(s.SuggestedValidBirthDate ? "بله" : "خیر")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>زادگاه</td>
|
||||
<td>@Model.LocationName(p.BirthLocationId)</td>
|
||||
<td>@(s.SuggestedBirthLocation != null ? s.SuggestedBirthLocation.Name : "")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>سال وفات (قمری)</td>
|
||||
<td>@(p.DeathYearInLHijri == 0 ? "" : p.DeathYearInLHijri.ToPersianNumbers())</td>
|
||||
<td>@(s.SuggestedDeathYearInLHijri == 0 ? "" : s.SuggestedDeathYearInLHijri.ToPersianNumbers())</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>تاریخ وفات مستند است</td>
|
||||
<td>@(p.ValidDeathDate ? "بله" : "خیر")</td>
|
||||
<td>@(s.SuggestedValidDeathDate ? "بله" : "خیر")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>آرامگاه</td>
|
||||
<td>@Model.LocationName(p.DeathLocationId)</td>
|
||||
<td>@(s.SuggestedDeathLocation != null ? s.SuggestedDeathLocation.Name : "")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>عنوان شجرهنامه</td>
|
||||
<td>@p.FamilyTreeCaption</td>
|
||||
<td>@s.SuggestedFamilyTreeCaption</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>توضیحات</td>
|
||||
<td><small>@p.Description</small></td>
|
||||
<td><small>@s.SuggestedDescription</small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@if (s.SuggestedForDeletion)
|
||||
{
|
||||
<div class="up-alert up-alert--danger">
|
||||
<strong>این پیشنهاد درخواست حذف کامل شخصیت «@p.Name» است.</strong>
|
||||
در صورت تأیید، این شخصیت و همهٔ نسبتهای خویشاوندی/وابستگیهای آن حذف خواهد شد و هرگونه پیشنهاد در حال بررسی مرتبط با آنها به طور خودکار رد میشود. فیلدهای زیر در این حالت نادیده گرفته میشوند.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="up-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>فیلد</th>
|
||||
<th>مقدار فعلی</th>
|
||||
<th>پیشنهاد</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>نام</td>
|
||||
<td>@p.Name</td>
|
||||
<td>@s.SuggestedName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>نشانی ویکیپدیا</td>
|
||||
<td>@p.WikiUrl</td>
|
||||
<td>@s.SuggestedWikiUrl</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>سال تولد (قمری)</td>
|
||||
<td>@(p.BirthYearInLHijri == 0 ? "" : p.BirthYearInLHijri.ToPersianNumbers())</td>
|
||||
<td>@(s.SuggestedBirthYearInLHijri == 0 ? "" : s.SuggestedBirthYearInLHijri.ToPersianNumbers())</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>تاریخ تولد مستند است</td>
|
||||
<td>@(p.ValidBirthDate ? "بله" : "خیر")</td>
|
||||
<td>@(s.SuggestedValidBirthDate ? "بله" : "خیر")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>زادگاه</td>
|
||||
<td>@Model.LocationName(p.BirthLocationId)</td>
|
||||
<td>@(s.SuggestedBirthLocation != null ? s.SuggestedBirthLocation.Name : "")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>سال وفات (قمری)</td>
|
||||
<td>@(p.DeathYearInLHijri == 0 ? "" : p.DeathYearInLHijri.ToPersianNumbers())</td>
|
||||
<td>@(s.SuggestedDeathYearInLHijri == 0 ? "" : s.SuggestedDeathYearInLHijri.ToPersianNumbers())</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>تاریخ وفات مستند است</td>
|
||||
<td>@(p.ValidDeathDate ? "بله" : "خیر")</td>
|
||||
<td>@(s.SuggestedValidDeathDate ? "بله" : "خیر")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>آرامگاه</td>
|
||||
<td>@Model.LocationName(p.DeathLocationId)</td>
|
||||
<td>@(s.SuggestedDeathLocation != null ? s.SuggestedDeathLocation.Name : "")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>عنوان شجرهنامه</td>
|
||||
<td>@p.FamilyTreeCaption</td>
|
||||
<td>@s.SuggestedFamilyTreeCaption</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>توضیحات</td>
|
||||
<td><small>@p.Description</small></td>
|
||||
<td><small>@s.SuggestedDescription</small></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
|
||||
199
GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml
Normal file
199
GanjooRazor/Areas/Admin/Pages/ReviewPersonRelationEdits.cshtml
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<div class="up-page-header">
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.FatalError))
|
||||
{
|
||||
<div class="up-alert up-alert--danger">@Model.FatalError</div>
|
||||
}
|
||||
else if (Model.Suggestion == null)
|
||||
{
|
||||
<partial name="_EmptyState" model="@("پیشنهاد ویرایشی وجود ندارد.")" />
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = Model.Suggestion;
|
||||
|
||||
<p>
|
||||
<span class="up-badge">@ActionName(s.Action)</span>
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>پیشنهاد دهنده:</td>
|
||||
<td>
|
||||
<a href="/user/edits/?allusers=1&userid=@s.UserId" target="_blank">@(s.User != null ? s.User.NickName : s.UserId.ToString())</a>
|
||||
— @s.Date.ToFriendlyPersianDateTextify()
|
||||
</td>
|
||||
</tr>
|
||||
@if (!string.IsNullOrEmpty(s.SuggestionNote))
|
||||
{
|
||||
<tr>
|
||||
<td>یادداشت پیشنهاددهنده:</td>
|
||||
<td>@s.SuggestionNote</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
|
||||
@if (s.Action == PersonRelationSuggestionAction.Remove)
|
||||
{
|
||||
<div class="up-alert up-alert--danger">
|
||||
<strong>این پیشنهاد درخواست حذف نسبت زیر است:</strong>
|
||||
</div>
|
||||
}
|
||||
|
||||
<table class="up-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>@(s.Action == PersonRelationSuggestionAction.Remove || s.Action == PersonRelationSuggestionAction.Modify ? "مقدار فعلی" : "پیشنهاد")</th>
|
||||
@if (s.Action == PersonRelationSuggestionAction.Modify)
|
||||
{
|
||||
<th>پیشنهاد</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>طرف اول</td>
|
||||
<td>
|
||||
<a role="button" target="_blank" href="/person/@s.Person1Id" class="up-btn up-btn--ghost up-btn--sm">@(s.Person1 != null ? s.Person1.Name : s.Person1Id.ToString())</a>
|
||||
</td>
|
||||
@if (s.Action == PersonRelationSuggestionAction.Modify)
|
||||
{
|
||||
<td>
|
||||
<a role="button" target="_blank" href="/person/@s.Person1Id" class="up-btn up-btn--ghost up-btn--sm">@(s.Person1 != null ? s.Person1.Name : s.Person1Id.ToString())</a>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>طرف دوم</td>
|
||||
<td>
|
||||
<a role="button" target="_blank" href="/person/@s.Person2Id" class="up-btn up-btn--ghost up-btn--sm">@(s.Person2 != null ? s.Person2.Name : s.Person2Id.ToString())</a>
|
||||
</td>
|
||||
@if (s.Action == PersonRelationSuggestionAction.Modify)
|
||||
{
|
||||
<td>
|
||||
<a role="button" target="_blank" href="/person/@s.Person2Id" class="up-btn up-btn--ghost up-btn--sm">@(s.Person2 != null ? s.Person2.Name : s.Person2Id.ToString())</a>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>نوع نسبت</td>
|
||||
<td>@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? RelationTypeName(s.ExistingRelation.RelationType) : RelationTypeName(s.SuggestedRelationType))</td>
|
||||
@if (s.Action == PersonRelationSuggestionAction.Modify)
|
||||
{
|
||||
<td>@RelationTypeName(s.SuggestedRelationType)</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>درجه (در نیا/نواده)</td>
|
||||
<td>@((s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.DegreeHint : s.SuggestedDegreeHint)?.ToString().ToPersianNumbers())</td>
|
||||
@if (s.Action == PersonRelationSuggestionAction.Modify)
|
||||
{
|
||||
<td>@s.SuggestedDegreeHint?.ToString().ToPersianNumbers()</td>
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>یادداشت</td>
|
||||
<td><small>@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.Note : s.SuggestedNote)</small></td>
|
||||
@if (s.Action == PersonRelationSuggestionAction.Modify)
|
||||
{
|
||||
<td><small>@s.SuggestedNote</small></td>
|
||||
}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label><input type="radio" name="review_result" id="review_result_Approved" value="Approved" checked="checked" /> تأیید</label>
|
||||
|
||||
<label><input type="radio" name="review_result" id="review_result_Rejected" value="Rejected" /> رد</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
یادداشت برای پیشنهاددهنده:<br />
|
||||
<textarea id="review_note" rows="3" cols="60"></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a role="button" id="moderatepersonrelationedit" class="up-btn up-btn--success up-btn--block" onclick="moderatePersonRelationEditSuggestion(@s.Id)">بررسی</a>
|
||||
</td>
|
||||
<td>
|
||||
<form method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" name="next" id="next" class="up-btn up-btn--ghost up-btn--block">بعدی</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p><small>@Model.Skip.ToPersianNumbers() از @Model.TotalCount.ToPersianNumbers()</small></p>
|
||||
|
||||
<script>
|
||||
function moderatePersonRelationEditSuggestion(id) {
|
||||
$('#moderatepersonrelationedit').hide();
|
||||
|
||||
var url = '?handler=Moderate';
|
||||
|
||||
var pms = {};
|
||||
pms.id = id;
|
||||
pms.result = $('input[name=review_result]:checked').val();
|
||||
pms.reviewNote = $('#review_note').val();
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: JSON.stringify(pms),
|
||||
contentType: "application/json; charset=utf-8",
|
||||
dataType: "json",
|
||||
success: function () {
|
||||
location.reload(true);
|
||||
},
|
||||
error: function (e) {
|
||||
upToast(e.responseText || 'خطا رخ داد.', 'error');
|
||||
$('#moderatepersonrelationedit').show();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class ReviewPersonRelationEditsModel : PageModel
|
||||
{
|
||||
/// <summary>
|
||||
/// the pending suggestion currently shown
|
||||
/// </summary>
|
||||
public GanjoorPersonRelationEditSuggestion Suggestion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// fatal error
|
||||
/// </summary>
|
||||
public string FatalError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// skip
|
||||
/// </summary>
|
||||
public int Skip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// total count
|
||||
/// </summary>
|
||||
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<IActionResult> 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<string>(await nextResponse.Content.ReadAsStringAsync());
|
||||
return Page();
|
||||
}
|
||||
|
||||
string paginationMetadata = nextResponse.Headers.GetValues("paging-headers").FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(paginationMetadata))
|
||||
{
|
||||
TotalCount = JsonConvert.DeserializeObject<PaginationMetadata>(paginationMetadata).totalCount;
|
||||
}
|
||||
|
||||
Suggestion = JsonConvert.DeserializeObject<GanjoorPersonRelationEditSuggestion>(await nextResponse.Content.ReadAsStringAsync());
|
||||
|
||||
return Page();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IActionResult> 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<string>(err);
|
||||
}
|
||||
return new BadRequestObjectResult(err);
|
||||
}
|
||||
|
||||
return new OkObjectResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BadRequestObjectResult("لطفاً از گنجور خارج و مجدداً به آن وارد شوید.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public class PersonRelationEditSuggestionModerationRequest
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Result { get; set; }
|
||||
public string ReviewNote { get; set; }
|
||||
}
|
||||
}
|
||||
@ -69,6 +69,10 @@ else
|
||||
<h2>
|
||||
خویشاوندان
|
||||
<a href="/FamilyTree/@Model.Person.Id" class="up-btn up-btn--ghost up-btn--sm">نمایش نمودار شجرهنامه</a>
|
||||
@if (Model.LoggedIn)
|
||||
{
|
||||
<a href="/SuggestNewPersonRelation/@Model.Person.Id" class="up-btn up-btn--ghost up-btn--sm">افزودن خویشاوند جدید</a>
|
||||
}
|
||||
</h2>
|
||||
<ul>
|
||||
@foreach (var row in Model.RelationRows)
|
||||
@ -79,11 +83,25 @@ else
|
||||
{
|
||||
<text> (@row.Note)</text>
|
||||
}
|
||||
@if (Model.LoggedIn)
|
||||
{
|
||||
<a href="/SuggestPersonRelationEdit/@row.RelationId" class="up-icon-btn" title="پیشنهاد ویرایش/حذف این نسبت" aria-label="پیشنهاد ویرایش/حذف این نسبت">✎</a>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else if (Model.LoggedIn)
|
||||
{
|
||||
<div class="spacer"> </div>
|
||||
<div class="poem">
|
||||
<h2>
|
||||
خویشاوندان
|
||||
<a href="/SuggestNewPersonRelation/@Model.Person.Id" class="up-btn up-btn--ghost up-btn--sm">افزودن خویشاوند جدید</a>
|
||||
</h2>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.AffiliationRows.Count > 0)
|
||||
{
|
||||
|
||||
@ -41,6 +41,12 @@ namespace GanjooRazor.Pages
|
||||
|
||||
public class PersonRelationDisplayRow
|
||||
{
|
||||
/// <summary>
|
||||
/// the underlying GanjoorPersonRelation row's own id - used to link to
|
||||
/// /SuggestPersonRelationEdit/{RelationId} for suggesting a change/removal of this edge
|
||||
/// </summary>
|
||||
public int RelationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the other person's role relative to the subject (e.g. "فرزند", "پدر/مادر", "نیا")
|
||||
/// </summary>
|
||||
@ -131,6 +137,7 @@ namespace GanjooRazor.Pages
|
||||
{
|
||||
RelationRows.Add(new PersonRelationDisplayRow()
|
||||
{
|
||||
RelationId = r.Id,
|
||||
Label = RelationLabel(r),
|
||||
OtherPersonId = r.OtherPersonId,
|
||||
OtherPersonName = r.OtherPersonName,
|
||||
|
||||
@ -482,6 +482,8 @@
|
||||
<a class="dropdown-item" asp-area="Admin" asp-page="ReviewPartEdits">ویرایشهای قطعات</a>
|
||||
<a class="dropdown-item" asp-area="Admin" asp-page="ReviewCatEdits">ویرایشهای زندگینامهها</a>
|
||||
<a class="dropdown-item" asp-area="Admin" asp-page="ReviewQuoteds">مشقها</a>
|
||||
<a class="dropdown-item" asp-area="Admin" asp-page="ReviewPersonEdits">ویرایشهای شخصیتها</a>
|
||||
<a class="dropdown-item" asp-area="Admin" asp-page="ReviewPersonRelationEdits">ویرایشهای نسبتهای خویشاوندی</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
||||
103
GanjooRazor/Pages/SuggestNewPersonRelation.cshtml
Normal file
103
GanjooRazor/Pages/SuggestNewPersonRelation.cshtml
Normal file
@ -0,0 +1,103 @@
|
||||
@page "{personId:int}"
|
||||
@model GanjooRazor.Pages.SuggestNewPersonRelationModel
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
ViewData["Title"] = "گنجور » پیشنهاد خویشاوند جدید";
|
||||
}
|
||||
@section Head {
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.LastError))
|
||||
{
|
||||
@Model.LastError
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- #hdr2 -->
|
||||
|
||||
<div id="fa">
|
||||
|
||||
<div id="maincnt">
|
||||
|
||||
<partial name="_InlineLoginPartial" model="Model" />
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
<div class="poem">
|
||||
<h1>پیشنهاد خویشاوند جدید برای: @Model.Person.Name</h1>
|
||||
|
||||
<p class="up-hint">
|
||||
این پیشنهاد مستقیماً اعمال نمیشود؛ پس از بررسی و تأیید توسط مدیران، به شجرهنامهٔ
|
||||
<a href="/person/@Model.Person.Id">@Model.Person.Name</a> افزوده خواهد شد.
|
||||
</p>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.LastResult))
|
||||
{
|
||||
<p>@Html.Raw(Model.LastResult)</p>
|
||||
}
|
||||
|
||||
<form method="post" action="/SuggestNewPersonRelation/@Model.Person.Id?Handler=Suggest">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>خویشاوند:</td>
|
||||
<td>
|
||||
<select asp-for="OtherPersonId">
|
||||
<option value="0">— انتخاب کنید —</option>
|
||||
@foreach (var p in Model.OtherPeople)
|
||||
{
|
||||
<option value="@p.Id">@p.Name</option>
|
||||
}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>نسبت با @Model.Person.Name:</td>
|
||||
<td>
|
||||
<select asp-for="RelationKind">
|
||||
<option value="Child">فرزند این شخصیت است</option>
|
||||
<option value="Parent">پدر یا مادر این شخصیت است</option>
|
||||
<option value="Sibling">خواهر یا برادر این شخصیت است</option>
|
||||
<option value="Spouse">همسر این شخصیت است</option>
|
||||
<option value="DistantAncestor">نیای دور این شخصیت است (بدون نسل مشخص)</option>
|
||||
<option value="DistantDescendant">نوادهٔ دور این شخصیت است (بدون نسل مشخص)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
درجه (فقط برای نیا/نواده):
|
||||
<p class="up-hint"><small>مثلاً برای پدربزرگ/مادربزرگ عدد ۲ را وارد کنید. اگر نمیدانید خالی بگذارید.</small></p>
|
||||
</td>
|
||||
<td><input asp-for="DegreeHint" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>یادداشت (مستندسازی):</td>
|
||||
<td><textarea asp-for="Note" rows="3" cols="60"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>یادداشت برای مدیر بررسیکننده:</td>
|
||||
<td><textarea asp-for="SuggestionNote" rows="3" cols="60"></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="ثبت پیشنهاد" class="up-btn up-btn--success" />
|
||||
<a role="button" href="/person/@Model.Person.Id" class="up-btn up-btn--ghost">انصراف</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
</div>
|
||||
|
||||
<partial name="_FooterPartial" model=@(new _FooterPartialModel(){StickyEnabled = true}) />
|
||||
|
||||
</div>
|
||||
|
||||
@if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString()))
|
||||
{
|
||||
@Html.Raw(ViewData["TrackingScript"])
|
||||
}
|
||||
}
|
||||
185
GanjooRazor/Pages/SuggestNewPersonRelation.cshtml.cs
Normal file
185
GanjooRazor/Pages/SuggestNewPersonRelation.cshtml.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<GanjoorRelatedPerson> 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<bool> 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<GanjoorRelatedPerson>(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<List<GanjoorRelatedPerson>>(await peopleResponse.Content.ReadAsStringAsync());
|
||||
OtherPeople = allPeople.Where(p => p.Id != id).ToList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(int personId)
|
||||
{
|
||||
InitializeCommonPageState();
|
||||
|
||||
if (!LoggedIn)
|
||||
{
|
||||
return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}");
|
||||
}
|
||||
|
||||
await PreparePersonAsync(personId);
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
|
||||
return Page();
|
||||
}
|
||||
|
||||
LastResult = $"پیشنهاد شما ثبت شد و پس از بررسی توسط مدیران اعمال خواهد شد. <a role=\"button\" href=\"/person/{personId}\" class=\"actionlink\">بازگشت به صفحهٔ شخصیت</a>";
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,6 +42,13 @@ else
|
||||
<input type="hidden" asp-for="Suggestion.PersonId" />
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>پیشنهاد حذف این شخصیت:</td>
|
||||
<td>
|
||||
<input asp-for="Suggestion.SuggestedForDeletion" id="suggestedForDeletion" />
|
||||
<p class="up-hint"><small>در صورت انتخاب این گزینه سایر فیلدها نادیده گرفته خواهد شد و در صورت تأیید مدیران، این شخصیت و همهٔ نسبتها/وابستگیهای آن حذف میشود.</small></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>نام:</td>
|
||||
<td><input asp-for="Suggestion.SuggestedName" size="40" /></td>
|
||||
|
||||
105
GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml
Normal file
105
GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml
Normal file
@ -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 {
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.LastError))
|
||||
{
|
||||
@Model.LastError
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- #hdr2 -->
|
||||
|
||||
<div id="fa">
|
||||
|
||||
<div id="maincnt">
|
||||
|
||||
<partial name="_InlineLoginPartial" model="Model" />
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
<div class="poem">
|
||||
<h1>
|
||||
پیشنهاد ویرایش نسبت:
|
||||
<a href="/person/@Model.Relation.Person1Id">@(Model.Relation.Person1 != null ? Model.Relation.Person1.Name : Model.Relation.Person1Id.ToString())</a>
|
||||
و
|
||||
<a href="/person/@Model.Relation.Person2Id">@(Model.Relation.Person2 != null ? Model.Relation.Person2.Name : Model.Relation.Person2Id.ToString())</a>
|
||||
</h1>
|
||||
|
||||
<p class="up-hint">
|
||||
این پیشنهاد مستقیماً اعمال نمیشود؛ پس از بررسی و تأیید توسط مدیران اعمال خواهد شد.
|
||||
</p>
|
||||
|
||||
@if (!string.IsNullOrEmpty(Model.LastResult))
|
||||
{
|
||||
<p>@Html.Raw(Model.LastResult)</p>
|
||||
}
|
||||
|
||||
<form method="post" action="/SuggestPersonRelationEdit/@Model.Relation.Id?Handler=SuggestEdit">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" asp-for="Suggestion.ExistingRelationId" />
|
||||
<input type="hidden" asp-for="Suggestion.Person1Id" />
|
||||
<input type="hidden" asp-for="Suggestion.Person2Id" />
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>نوع درخواست:</td>
|
||||
<td>
|
||||
<label><input type="radio" name="Action" value="Modify" checked="checked" /> ویرایش این نسبت</label>
|
||||
|
||||
<label><input type="radio" name="Action" value="Remove" /> حذف این نسبت (اشتباه ثبت شده است)</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>نوع نسبت:</td>
|
||||
<td>
|
||||
<select asp-for="Suggestion.SuggestedRelationType">
|
||||
<option value="@((int)PersonRelationType.Parent)">پدر/مادر و فرزند (طرف اول، پدر/مادر است)</option>
|
||||
<option value="@((int)PersonRelationType.Sibling)">خواهر/برادر</option>
|
||||
<option value="@((int)PersonRelationType.Spouse)">همسر</option>
|
||||
<option value="@((int)PersonRelationType.Ancestor)">نیا و نواده (طرف اول، نیاست)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
درجه (فقط برای نیا/نواده):
|
||||
<p class="up-hint"><small>مثلاً برای پدربزرگ/مادربزرگ عدد ۲ را وارد کنید. اگر نمیدانید خالی بگذارید.</small></p>
|
||||
</td>
|
||||
<td><input asp-for="Suggestion.SuggestedDegreeHint" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>یادداشت (مستندسازی):</td>
|
||||
<td><textarea asp-for="Suggestion.SuggestedNote" rows="3" cols="60"></textarea></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>یادداشت برای مدیر بررسیکننده:</td>
|
||||
<td><textarea asp-for="Suggestion.SuggestionNote" rows="3" cols="60"></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="ثبت پیشنهاد" class="up-btn up-btn--success" />
|
||||
<a role="button" href="/person/@Model.Relation.Person1Id" class="up-btn up-btn--ghost">انصراف</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="spacer"> </div>
|
||||
|
||||
</div>
|
||||
|
||||
<partial name="_FooterPartial" model=@(new _FooterPartialModel(){StickyEnabled = true}) />
|
||||
|
||||
</div>
|
||||
|
||||
@if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString()))
|
||||
{
|
||||
@Html.Raw(ViewData["TrackingScript"])
|
||||
}
|
||||
}
|
||||
133
GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml.cs
Normal file
133
GanjooRazor/Pages/SuggestPersonRelationEdit.cshtml.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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<bool> 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<GanjoorPersonRelation>(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<IActionResult> 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<IActionResult> 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<string>(await response.Content.ReadAsStringAsync());
|
||||
FillSuggestionFromCurrentRelation();
|
||||
return Page();
|
||||
}
|
||||
|
||||
LastResult = $"پیشنهاد شما ثبت شد و پس از بررسی توسط مدیران اعمال خواهد شد. <a role=\"button\" href=\"/person/{Relation.Person1Id}\" class=\"actionlink\">بازگشت به صفحهٔ شخصیت</a>";
|
||||
|
||||
FillSuggestionFromCurrentRelation();
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,6 +207,100 @@ namespace RMuseum.Controllers
|
||||
return Ok(res.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get a single kinship edge by its own id, with both sides' names resolved - used by
|
||||
/// /SuggestPersonRelationEdit/{relationId} to show what it's about
|
||||
/// </summary>
|
||||
/// <param name="relationId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("relations/{relationId:int}")]
|
||||
[AllowAnonymous]
|
||||
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelation))]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
|
||||
public async Task<IActionResult> GetRelationByIdAsync(int relationId)
|
||||
{
|
||||
var res = await _personService.GetRelationByIdAsync(relationId);
|
||||
if (!string.IsNullOrEmpty(res.ExceptionString))
|
||||
return BadRequest(res.ExceptionString);
|
||||
return Ok(res.Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="suggestion"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("relationeditsuggestion")]
|
||||
[Authorize]
|
||||
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelationEditSuggestion))]
|
||||
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
|
||||
public async Task<IActionResult> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get the next unreviewed relation-edit suggestion, for the moderator queue - same
|
||||
/// permission as reviewing a person edit
|
||||
/// </summary>
|
||||
/// <param name="skip"></param>
|
||||
/// <returns></returns>
|
||||
[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<IActionResult> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// apply a moderator's decision to a pending relation-edit suggestion - same permission as
|
||||
/// moderating a person edit
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="moderation"></param>
|
||||
/// <returns></returns>
|
||||
[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<IActionResult> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// person service
|
||||
/// </summary>
|
||||
|
||||
@ -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<GanjoorPersonRelationEditSuggestion>()
|
||||
.HasOne(s => s.Person1)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.Person1Id)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<GanjoorPersonRelationEditSuggestion>()
|
||||
.HasOne(s => s.Person2)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.Person2Id)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<GanjoorPersonRelationEditSuggestion>()
|
||||
.HasOne(s => s.ExistingRelation)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.ExistingRelationId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<GanjoorUserBookmark>()
|
||||
.HasIndex(b => new { b.UserId, b.PoemId, b.CoupletIndex });
|
||||
|
||||
@ -625,6 +648,12 @@ namespace RMuseum.DbContext
|
||||
/// </summary>
|
||||
public DbSet<GanjoorPersonEditSuggestion> GanjoorPersonEditSuggestions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// pending/reviewed suggested additions, changes or removals of a kinship edge between two
|
||||
/// already-approved people - see GanjoorPersonRelationEditSuggestion
|
||||
/// </summary>
|
||||
public DbSet<GanjoorPersonRelationEditSuggestion> GanjoorPersonRelationEditSuggestions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Books (PDF Library)
|
||||
/// </summary>
|
||||
|
||||
6595
RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.Designer.cs
generated
Normal file
6595
RMuseum/Migrations/20260929160106_AddGanjoorPersonRelationEditSuggestion.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace RMuseum.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddGanjoorPersonRelationEditSuggestion : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "SuggestedForDeletion",
|
||||
table: "GanjoorPersonEditSuggestions",
|
||||
type: "bit",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GanjoorPersonRelationEditSuggestions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Action = table.Column<int>(type: "int", nullable: false),
|
||||
ExistingRelationId = table.Column<int>(type: "int", nullable: true),
|
||||
Person1Id = table.Column<int>(type: "int", nullable: false),
|
||||
Person2Id = table.Column<int>(type: "int", nullable: false),
|
||||
SuggestedRelationType = table.Column<int>(type: "int", nullable: false),
|
||||
SuggestedDegreeHint = table.Column<int>(type: "int", nullable: true),
|
||||
SuggestedNote = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
SuggestionNote = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Date = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
Reviewed = table.Column<bool>(type: "bit", nullable: false),
|
||||
Result = table.Column<int>(type: "int", nullable: false),
|
||||
ReviewDate = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ReviewNote = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ReviewerUserId = table.Column<Guid>(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");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GanjoorPersonRelationEditSuggestions");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SuggestedForDeletion",
|
||||
table: "GanjoorPersonEditSuggestions");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1475,6 +1475,9 @@ namespace RMuseum.Migrations
|
||||
b.Property<string>("SuggestedFamilyTreeCaption")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("SuggestedForDeletion")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SuggestedName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
@ -1540,6 +1543,74 @@ namespace RMuseum.Migrations
|
||||
b.ToTable("GanjoorPersonRelations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("Action")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("Date")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<int?>("ExistingRelationId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Person1Id")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Person2Id")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Result")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("ReviewDate")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ReviewNote")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("Reviewed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<Guid?>("ReviewerUserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("SuggestedDegreeHint")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SuggestedNote")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("SuggestedRelationType")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SuggestionNote")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("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<int>("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")
|
||||
|
||||
@ -108,6 +108,16 @@ namespace RMuseum.Models.Ganjoor
|
||||
/// </summary>
|
||||
public string SuggestedFamilyTreeCaption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool SuggestedForDeletion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// suggester's note to the moderator
|
||||
/// </summary>
|
||||
|
||||
128
RMuseum/Models/Ganjoor/GanjoorPersonRelationEditSuggestion.cs
Normal file
128
RMuseum/Models/Ganjoor/GanjoorPersonRelationEditSuggestion.cs
Normal file
@ -0,0 +1,128 @@
|
||||
using RSecurityBackend.Models.Auth.Db;
|
||||
using System;
|
||||
|
||||
namespace RMuseum.Models.Ganjoor
|
||||
{
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public class GanjoorPersonRelationEditSuggestion
|
||||
{
|
||||
/// <summary>
|
||||
/// suggestion id
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// what this suggestion proposes doing
|
||||
/// </summary>
|
||||
public PersonRelationSuggestionAction Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// for Modify/Remove: the existing GanjoorPersonRelation row this suggestion targets. Null
|
||||
/// for Add, where there's nothing existing yet.
|
||||
/// </summary>
|
||||
public int? ExistingRelationId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the existing relation being modified/removed (for showing a before/after diff to the
|
||||
/// moderator) - null for Add
|
||||
/// </summary>
|
||||
public virtual GanjoorPersonRelation ExistingRelation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public int Person1Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// first person (navigation)
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// second person id, see Person1Id
|
||||
/// </summary>
|
||||
public int Person2Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// second person (navigation)
|
||||
/// </summary>
|
||||
public virtual GanjoorRelatedPerson Person2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public PersonRelationType SuggestedRelationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// see GanjoorPersonRelation.DegreeHint - same for/Add/Modify/Remove-display purpose as
|
||||
/// SuggestedRelationType above
|
||||
/// </summary>
|
||||
public int? SuggestedDegreeHint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// see GanjoorPersonRelation.Note - same Add/Modify/Remove-display purpose as
|
||||
/// SuggestedRelationType above
|
||||
/// </summary>
|
||||
public string SuggestedNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// suggester's note to the moderator (e.g. why an existing relation is wrong)
|
||||
/// </summary>
|
||||
public string SuggestionNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// date
|
||||
/// </summary>
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// suggester's user id
|
||||
/// </summary>
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// suggester
|
||||
/// </summary>
|
||||
public virtual RAppUser User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// reviewed
|
||||
/// </summary>
|
||||
public bool Reviewed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// review result
|
||||
/// </summary>
|
||||
public CorrectionReviewResult Result { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// review date
|
||||
/// </summary>
|
||||
public DateTime ReviewDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// review note
|
||||
/// </summary>
|
||||
public string ReviewNote { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// reviewer's user id
|
||||
/// </summary>
|
||||
public Guid? ReviewerUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// reviewer
|
||||
/// </summary>
|
||||
public virtual RAppUser ReviewerUser { get; set; }
|
||||
}
|
||||
}
|
||||
23
RMuseum/Models/Ganjoor/PersonRelationSuggestionAction.cs
Normal file
23
RMuseum/Models/Ganjoor/PersonRelationSuggestionAction.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace RMuseum.Models.Ganjoor
|
||||
{
|
||||
/// <summary>
|
||||
/// what a GanjoorPersonRelationEditSuggestion proposes doing to the kinship graph
|
||||
/// </summary>
|
||||
public enum PersonRelationSuggestionAction
|
||||
{
|
||||
/// <summary>
|
||||
/// create a brand new GanjoorPersonRelation between Person1Id and Person2Id
|
||||
/// </summary>
|
||||
Add = 0,
|
||||
|
||||
/// <summary>
|
||||
/// change the RelationType/DegreeHint/Note of the existing relation ExistingRelationId points to
|
||||
/// </summary>
|
||||
Modify = 1,
|
||||
|
||||
/// <summary>
|
||||
/// remove the existing relation ExistingRelationId points to outright
|
||||
/// </summary>
|
||||
Remove = 2,
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,12 @@ namespace RMuseum.Models.Ganjoor.ViewModels
|
||||
/// </summary>
|
||||
public class GanjoorPersonRelationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the other person in this relation
|
||||
/// </summary>
|
||||
|
||||
@ -2894,6 +2894,39 @@
|
||||
<param name="moderation"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetRelationByIdAsync(System.Int32)">
|
||||
<summary>
|
||||
get a single kinship edge by its own id, with both sides' names resolved - used by
|
||||
/SuggestPersonRelationEdit/{relationId} to show what it's about
|
||||
</summary>
|
||||
<param name="relationId"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.SuggestPersonRelationEditAsync(RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion)">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
<param name="suggestion"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetNextUnreviewedPersonRelationEditSuggestionAsync(System.Int32)">
|
||||
<summary>
|
||||
get the next unreviewed relation-edit suggestion, for the moderator queue - same
|
||||
permission as reviewing a person edit
|
||||
</summary>
|
||||
<param name="skip"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.ModeratePersonRelationEditSuggestionAsync(System.Int32,RMuseum.Models.Ganjoor.ViewModels.PersonEditSuggestionModerationViewModel)">
|
||||
<summary>
|
||||
apply a moderator's decision to a pending relation-edit suggestion - same permission as
|
||||
moderating a person edit
|
||||
</summary>
|
||||
<param name="id"></param>
|
||||
<param name="moderation"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="F:RMuseum.Controllers.GanjoorRelatedPersonController._personService">
|
||||
<summary>
|
||||
person service
|
||||
@ -4030,6 +4063,12 @@
|
||||
see GanjoorPersonEditSuggestion
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.DbContext.RMuseumDbContext.GanjoorPersonRelationEditSuggestions">
|
||||
<summary>
|
||||
pending/reviewed suggested additions, changes or removals of a kinship edge between two
|
||||
already-approved people - see GanjoorPersonRelationEditSuggestion
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.DbContext.RMuseumDbContext.Books">
|
||||
<summary>
|
||||
Books (PDF Library)
|
||||
@ -4877,6 +4916,18 @@
|
||||
<member name="M:RMuseum.Migrations.AddGanjoorPersonEditSuggestion.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:RMuseum.Migrations.AddGanjoorPersonRelationEditSuggestion">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:RMuseum.Migrations.AddGanjoorPersonRelationEditSuggestion.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:RMuseum.Migrations.AddGanjoorPersonRelationEditSuggestion.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:RMuseum.Migrations.AddGanjoorPersonRelationEditSuggestion.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:RMuseum.Models.Accounting.DonationExpenditure">
|
||||
<summary>
|
||||
donation expenditures
|
||||
@ -9396,6 +9447,16 @@
|
||||
suggested family tree caption
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedForDeletion">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestionNote">
|
||||
<summary>
|
||||
suggester's note to the moderator
|
||||
@ -9483,6 +9544,128 @@
|
||||
free-text note (e.g. sourcing/reasoning for this relation)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion">
|
||||
<summary>
|
||||
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).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Id">
|
||||
<summary>
|
||||
suggestion id
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Action">
|
||||
<summary>
|
||||
what this suggestion proposes doing
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ExistingRelationId">
|
||||
<summary>
|
||||
for Modify/Remove: the existing GanjoorPersonRelation row this suggestion targets. Null
|
||||
for Add, where there's nothing existing yet.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ExistingRelation">
|
||||
<summary>
|
||||
the existing relation being modified/removed (for showing a before/after diff to the
|
||||
moderator) - null for Add
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Person1Id">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Person1">
|
||||
<summary>
|
||||
first person (navigation)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Person2Id">
|
||||
<summary>
|
||||
second person id, see Person1Id
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Person2">
|
||||
<summary>
|
||||
second person (navigation)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestedRelationType">
|
||||
<summary>
|
||||
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).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestedDegreeHint">
|
||||
<summary>
|
||||
see GanjoorPersonRelation.DegreeHint - same for/Add/Modify/Remove-display purpose as
|
||||
SuggestedRelationType above
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestedNote">
|
||||
<summary>
|
||||
see GanjoorPersonRelation.Note - same Add/Modify/Remove-display purpose as
|
||||
SuggestedRelationType above
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestionNote">
|
||||
<summary>
|
||||
suggester's note to the moderator (e.g. why an existing relation is wrong)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Date">
|
||||
<summary>
|
||||
date
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.UserId">
|
||||
<summary>
|
||||
suggester's user id
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.User">
|
||||
<summary>
|
||||
suggester
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Reviewed">
|
||||
<summary>
|
||||
reviewed
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Result">
|
||||
<summary>
|
||||
review result
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ReviewDate">
|
||||
<summary>
|
||||
review date
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ReviewNote">
|
||||
<summary>
|
||||
review note
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ReviewerUserId">
|
||||
<summary>
|
||||
reviewer's user id
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ReviewerUser">
|
||||
<summary>
|
||||
reviewer
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:RMuseum.Models.Ganjoor.GanjoorPoem">
|
||||
<summary>
|
||||
Ganjoor Poem
|
||||
@ -11571,6 +11754,26 @@
|
||||
doesn't fit any of the above - rely on Note for what the tie actually is
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:RMuseum.Models.Ganjoor.PersonRelationSuggestionAction">
|
||||
<summary>
|
||||
what a GanjoorPersonRelationEditSuggestion proposes doing to the kinship graph
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:RMuseum.Models.Ganjoor.PersonRelationSuggestionAction.Add">
|
||||
<summary>
|
||||
create a brand new GanjoorPersonRelation between Person1Id and Person2Id
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:RMuseum.Models.Ganjoor.PersonRelationSuggestionAction.Modify">
|
||||
<summary>
|
||||
change the RelationType/DegreeHint/Note of the existing relation ExistingRelationId points to
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:RMuseum.Models.Ganjoor.PersonRelationSuggestionAction.Remove">
|
||||
<summary>
|
||||
remove the existing relation ExistingRelationId points to outright
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:RMuseum.Models.Ganjoor.PersonRelationType">
|
||||
<summary>
|
||||
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
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.Id">
|
||||
<summary>
|
||||
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
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.OtherPersonId">
|
||||
<summary>
|
||||
the other person in this relation
|
||||
@ -18008,6 +18217,48 @@
|
||||
<param name="reviewNote"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetRelationByIdAsync(System.Int32)">
|
||||
<summary>
|
||||
get a single kinship edge by its own id, with both sides' names resolved - used by
|
||||
/SuggestPersonRelationEdit/{relationId} to show what it's about
|
||||
</summary>
|
||||
<param name="relationId"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.SuggestPersonRelationEditAsync(RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion)">
|
||||
<summary>
|
||||
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
|
||||
</summary>
|
||||
<param name="suggestion"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetNextUnreviewedPersonRelationEditSuggestionAsync(System.Int32)">
|
||||
<summary>
|
||||
get the next unreviewed relation-edit suggestion (for the moderator queue)
|
||||
</summary>
|
||||
<param name="skip"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetUnreviewedPersonRelationEditSuggestionCountAsync">
|
||||
<summary>
|
||||
unreviewed relation-edit suggestion count
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.ModeratePersonRelationEditSuggestionAsync(System.Guid,System.Int32,RMuseum.Models.Ganjoor.CorrectionReviewResult,System.String)">
|
||||
<summary>
|
||||
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).
|
||||
</summary>
|
||||
<param name="moderatorUserId"></param>
|
||||
<param name="suggestionId"></param>
|
||||
<param name="result"></param>
|
||||
<param name="reviewNote"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="T:RMuseum.Services.IGanjoorService">
|
||||
<summary>
|
||||
Ganjoor Poems Content Privider Service
|
||||
@ -21514,16 +21765,82 @@
|
||||
<param name="reviewNote"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.DeletePersonAndReferencesAsync(RMuseum.Models.Ganjoor.GanjoorRelatedPerson)">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetRelationByIdAsync(System.Int32)">
|
||||
<summary>
|
||||
get a single kinship edge by its own id, with both sides' names resolved
|
||||
</summary>
|
||||
<param name="relationId"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.SuggestPersonRelationEditAsync(RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion)">
|
||||
<summary>
|
||||
submit a suggested addition, change or removal of a kinship edge
|
||||
</summary>
|
||||
<param name="suggestion"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetNextUnreviewedPersonRelationEditSuggestionAsync(System.Int32)">
|
||||
<summary>
|
||||
get the next unreviewed relation-edit suggestion for the moderator queue
|
||||
</summary>
|
||||
<param name="skip"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetUnreviewedPersonRelationEditSuggestionCountAsync">
|
||||
<summary>
|
||||
unreviewed relation-edit suggestion count
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.ModeratePersonRelationEditSuggestionAsync(System.Guid,System.Int32,RMuseum.Models.Ganjoor.CorrectionReviewResult,System.String)">
|
||||
<summary>
|
||||
apply a moderator's decision to a pending relation-edit suggestion
|
||||
</summary>
|
||||
<param name="moderatorUserId"></param>
|
||||
<param name="suggestionId"></param>
|
||||
<param name="result"></param>
|
||||
<param name="reviewNote"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="F:RMuseum.Services.Implementation.GanjoorRelatedPersonService._context">
|
||||
<summary>
|
||||
Database Context
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.#ctor(RMuseum.DbContext.RMuseumDbContext)">
|
||||
<member name="F:RMuseum.Services.Implementation.GanjoorRelatedPersonService._appUserService">
|
||||
<summary>
|
||||
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
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:RMuseum.Services.Implementation.GanjoorRelatedPersonService._notificationService">
|
||||
<summary>
|
||||
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
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.#ctor(RMuseum.DbContext.RMuseumDbContext,RSecurityBackend.Services.IAppUserService,RSecurityBackend.Services.IRNotificationService)">
|
||||
<summary>
|
||||
constructor
|
||||
</summary>
|
||||
<param name="context"></param>
|
||||
<param name="appUserService"></param>
|
||||
<param name="notificationService"></param>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.NotifyModeratorsOfPendingSuggestionAsync(System.String,System.String)">
|
||||
<summary>
|
||||
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
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:RMuseum.Services.Implementation.GanjoorService">
|
||||
<summary>
|
||||
|
||||
@ -96,5 +96,47 @@ namespace RMuseum.Services
|
||||
/// <param name="reviewNote"></param>
|
||||
/// <returns></returns>
|
||||
Task<RServiceResult<GanjoorPersonEditSuggestion>> ModeratePersonEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote);
|
||||
|
||||
/// <summary>
|
||||
/// get a single kinship edge by its own id, with both sides' names resolved - used by
|
||||
/// /SuggestPersonRelationEdit/{relationId} to show what it's about
|
||||
/// </summary>
|
||||
/// <param name="relationId"></param>
|
||||
/// <returns></returns>
|
||||
Task<RServiceResult<GanjoorPersonRelation>> GetRelationByIdAsync(int relationId);
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="suggestion"></param>
|
||||
/// <returns></returns>
|
||||
Task<RServiceResult<GanjoorPersonRelationEditSuggestion>> SuggestPersonRelationEditAsync(GanjoorPersonRelationEditSuggestion suggestion);
|
||||
|
||||
/// <summary>
|
||||
/// get the next unreviewed relation-edit suggestion (for the moderator queue)
|
||||
/// </summary>
|
||||
/// <param name="skip"></param>
|
||||
/// <returns></returns>
|
||||
Task<RServiceResult<GanjoorPersonRelationEditSuggestion>> GetNextUnreviewedPersonRelationEditSuggestionAsync(int skip);
|
||||
|
||||
/// <summary>
|
||||
/// unreviewed relation-edit suggestion count
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<RServiceResult<int>> GetUnreviewedPersonRelationEditSuggestionCountAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
/// <param name="moderatorUserId"></param>
|
||||
/// <param name="suggestionId"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="reviewNote"></param>
|
||||
/// <returns></returns>
|
||||
Task<RServiceResult<GanjoorPersonRelationEditSuggestion>> ModeratePersonRelationEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<GanjoorPersonEditSuggestion>(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<GanjoorPersonEditSuggestion>(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}» پیشنهاد داده است.") +
|
||||
$" لطفاً بخش <a href=\"https://ganjoor.net/Admin/ReviewPersonEdits\">ویرایشهای پیشنهادی شخصیتها</a> را بررسی فرمایید."
|
||||
);
|
||||
|
||||
return new RServiceResult<GanjoorPersonEditSuggestion>(suggestion);
|
||||
}
|
||||
catch (Exception exp)
|
||||
@ -367,25 +380,33 @@ namespace RMuseum.Services.Implementation
|
||||
return new RServiceResult<GanjoorPersonEditSuggestion>(null, "این پیشنهاد پیشتر بررسی شده است.");
|
||||
}
|
||||
|
||||
var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).SingleOrDefaultAsync();
|
||||
if (person == null)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonEditSuggestion>(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<GanjoorPersonEditSuggestion>(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<GanjoorPersonEditSuggestion>(suggestion);
|
||||
}
|
||||
catch (Exception exp)
|
||||
@ -404,18 +448,407 @@ namespace RMuseum.Services.Implementation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get a single kinship edge by its own id, with both sides' names resolved
|
||||
/// </summary>
|
||||
/// <param name="relationId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RServiceResult<GanjoorPersonRelation>> 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<GanjoorPersonRelation>(null, "نسبت پیدا نشد.");
|
||||
}
|
||||
|
||||
return new RServiceResult<GanjoorPersonRelation>(relation);
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelation>(null, exp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// submit a suggested addition, change or removal of a kinship edge
|
||||
/// </summary>
|
||||
/// <param name="suggestion"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RServiceResult<GanjoorPersonRelationEditSuggestion>> SuggestPersonRelationEditAsync(GanjoorPersonRelationEditSuggestion suggestion)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (suggestion == null)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "اطلاعات پیشنهاد ناقص است.");
|
||||
}
|
||||
|
||||
GanjoorPersonRelation existingRelation = null;
|
||||
if (suggestion.Action == PersonRelationSuggestionAction.Modify || suggestion.Action == PersonRelationSuggestionAction.Remove)
|
||||
{
|
||||
if (suggestion.ExistingRelationId == null)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(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<GanjoorPersonRelationEditSuggestion>(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<GanjoorPersonRelationEditSuggestion>(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<GanjoorPersonRelationEditSuggestion>(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 + " لطفاً بخش <a href=\"https://ganjoor.net/Admin/ReviewPersonRelationEdits\">ویرایشهای پیشنهادی نسبتهای خویشاوندی</a> را بررسی فرمایید."
|
||||
);
|
||||
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(suggestion);
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, exp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get the next unreviewed relation-edit suggestion for the moderator queue
|
||||
/// </summary>
|
||||
/// <param name="skip"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RServiceResult<GanjoorPersonRelationEditSuggestion>> 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<GanjoorPersonRelationEditSuggestion>(suggestion);
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, exp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// unreviewed relation-edit suggestion count
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<RServiceResult<int>> GetUnreviewedPersonRelationEditSuggestionCountAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RServiceResult<int>(await _context.GanjoorPersonRelationEditSuggestions.Where(s => s.Reviewed == false).CountAsync());
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
return new RServiceResult<int>(0, exp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// apply a moderator's decision to a pending relation-edit suggestion
|
||||
/// </summary>
|
||||
/// <param name="moderatorUserId"></param>
|
||||
/// <param name="suggestionId"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="reviewNote"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<RServiceResult<GanjoorPersonRelationEditSuggestion>> 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<GanjoorPersonRelationEditSuggestion>(null, "پیشنهاد پیدا نشد.");
|
||||
}
|
||||
|
||||
if (suggestion.Reviewed)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(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<GanjoorPersonRelationEditSuggestion>(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<GanjoorPersonRelationEditSuggestion>(suggestion);
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, exp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Database Context
|
||||
/// </summary>
|
||||
protected readonly RMuseumDbContext _context;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
protected readonly IAppUserService _appUserService;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
protected readonly IRNotificationService _notificationService;
|
||||
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public GanjoorRelatedPersonService(RMuseumDbContext context)
|
||||
/// <param name="appUserService"></param>
|
||||
/// <param name="notificationService"></param>
|
||||
public GanjoorRelatedPersonService(RMuseumDbContext context, IAppUserService appUserService, IRNotificationService notificationService)
|
||||
{
|
||||
_context = context;
|
||||
_appUserService = appUserService;
|
||||
_notificationService = notificationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user