people tags #387
This commit is contained in:
parent
20b13cd0e7
commit
1c65da5ade
@ -958,6 +958,7 @@ else
|
|||||||
@if (geoTag.PersonId != null)
|
@if (geoTag.PersonId != null)
|
||||||
{
|
{
|
||||||
<text>شخصیت: @(geoTag.Person != null ? geoTag.Person.Name : $"شخصیت #{geoTag.PersonId}")</text>
|
<text>شخصیت: @(geoTag.Person != null ? geoTag.Person.Name : $"شخصیت #{geoTag.PersonId}")</text>
|
||||||
|
<a href="/SuggestPersonEdit/@geoTag.PersonId" target="_blank" class="up-icon-btn" title="پیشنهاد ویرایش این شخصیت" aria-label="پیشنهاد ویرایش این شخصیت">✎</a>
|
||||||
}
|
}
|
||||||
else if (personGraph?.Person != null)
|
else if (personGraph?.Person != null)
|
||||||
{
|
{
|
||||||
|
|||||||
168
GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml
Normal file
168
GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
@page
|
||||||
|
@model GanjooRazor.Areas.Admin.Pages.ReviewPersonEditsModel
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@using DNTPersianUtils.Core
|
||||||
|
@{
|
||||||
|
Layout = "_AdminLayout";
|
||||||
|
ViewData["Title"] = "بازبینی پیشنهادهای ویرایش شخصیتها";
|
||||||
|
}
|
||||||
|
|
||||||
|
<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;
|
||||||
|
var p = s.Person;
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a role="button" target="_blank" href="/person/@p.Id" class="up-btn up-btn--ghost up-btn--sm">@p.Name</a>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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="moderatepersonedit" class="up-btn up-btn--success up-btn--block" onclick="moderatePersonEditSuggestion(@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 moderatePersonEditSuggestion(id) {
|
||||||
|
$('#moderatepersonedit').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');
|
||||||
|
$('#moderatepersonedit').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
178
GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml.cs
Normal file
178
GanjooRazor/Areas/Admin/Pages/ReviewPersonEdits.cshtml.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
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.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 GanjoorPersonEditSuggestion entries - counterpart of
|
||||||
|
/// ReviewEdits.cshtml for poem corrections, but much simpler: one suggestion, one overall
|
||||||
|
/// Result, no per-field branching.
|
||||||
|
/// </summary>
|
||||||
|
public class ReviewPersonEditsModel : PageModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// the pending suggestion currently shown
|
||||||
|
/// </summary>
|
||||||
|
public GanjoorPersonEditSuggestion 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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// full location catalog, used only to resolve the CURRENT person's BirthLocationId/
|
||||||
|
/// DeathLocationId into names for the before/after diff (the suggestion's own Suggested*
|
||||||
|
/// locations already arrive populated via the backend's Include)
|
||||||
|
/// </summary>
|
||||||
|
public List<GanjoorGeoLocation> Locations { get; set; }
|
||||||
|
|
||||||
|
public string LocationName(int? id)
|
||||||
|
{
|
||||||
|
if (id == null || Locations == null)
|
||||||
|
return "";
|
||||||
|
var loc = Locations.Where(l => l.Id == id).FirstOrDefault();
|
||||||
|
return loc == null ? "" : loc.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnPost()
|
||||||
|
{
|
||||||
|
Skip = string.IsNullOrEmpty(Request.Query["skip"]) ? 0 : int.Parse(Request.Query["skip"]);
|
||||||
|
if (Request.Form["next"].Count == 1)
|
||||||
|
{
|
||||||
|
return Redirect($"/Admin/ReviewPersonEdits/?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 locationsResponse = await secureClient.GetAsync($"{APIRoot.Url}/api/locations");
|
||||||
|
if (!locationsResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
FatalError = JsonConvert.DeserializeObject<string>(await locationsResponse.Content.ReadAsStringAsync());
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
Locations = JsonConvert.DeserializeObject<List<GanjoorGeoLocation>>(await locationsResponse.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
var nextResponse = await secureClient.GetAsync($"{APIRoot.Url}/api/people/editsuggestions/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<GanjoorPersonEditSuggestion>(await nextResponse.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Redirect("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostModerateAsync([FromBody] PersonEditSuggestionModerationRequest 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/editsuggestions/{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 ReviewPersonEdits.cshtml's moderatePersonEditSuggestion() ajax call -
|
||||||
|
/// kept local to this page since it's only ever used to bind that one handler's [FromBody] parameter
|
||||||
|
/// </summary>
|
||||||
|
public class PersonEditSuggestionModerationRequest
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Result { get; set; }
|
||||||
|
public string ReviewNote { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,7 +24,13 @@ else
|
|||||||
<div class="spacer"> </div>
|
<div class="spacer"> </div>
|
||||||
|
|
||||||
<div class="poem">
|
<div class="poem">
|
||||||
<h1>@Model.Person.Name</h1>
|
<h1>
|
||||||
|
@Model.Person.Name
|
||||||
|
@if (Model.LoggedIn)
|
||||||
|
{
|
||||||
|
<a href="/SuggestPersonEdit/@Model.Person.Id" class="up-icon-btn" title="پیشنهاد ویرایش این شخصیت" aria-label="پیشنهاد ویرایش این شخصیت">✎</a>
|
||||||
|
}
|
||||||
|
</h1>
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Model.Person.FamilyTreeCaption))
|
@if (!string.IsNullOrEmpty(Model.Person.FamilyTreeCaption))
|
||||||
{
|
{
|
||||||
|
|||||||
125
GanjooRazor/Pages/SuggestPersonEdit.cshtml
Normal file
125
GanjooRazor/Pages/SuggestPersonEdit.cshtml
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
@page "{id:int}"
|
||||||
|
@model GanjooRazor.Pages.SuggestPersonEditModel
|
||||||
|
@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="/SuggestPersonEdit/@Model.Person.Id?Handler=SuggestEdit">
|
||||||
|
@Html.AntiForgeryToken()
|
||||||
|
<input type="hidden" asp-for="Suggestion.PersonId" />
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>نام:</td>
|
||||||
|
<td><input asp-for="Suggestion.SuggestedName" size="40" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>نشانی ویکیپدیا:</td>
|
||||||
|
<td><input style="text-align:left;direction:ltr;" asp-for="Suggestion.SuggestedWikiUrl" size="60" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>زاده:</td>
|
||||||
|
<td><input asp-for="Suggestion.SuggestedBirthYearInLHijri" /> (قمری)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>تاریخ تولد مستند است:</td>
|
||||||
|
<td><input asp-for="Suggestion.SuggestedValidBirthDate" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>زادگاه:</td>
|
||||||
|
<td>
|
||||||
|
<select asp-for="Suggestion.SuggestedBirthLocationId">
|
||||||
|
@foreach (var location in Model.Locations)
|
||||||
|
{
|
||||||
|
<option value="@(location.Id == 0 ? "" : location.Id.ToString())">@location.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>درگذشته:</td>
|
||||||
|
<td><input asp-for="Suggestion.SuggestedDeathYearInLHijri" /> (قمری)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>تاریخ وفات مستند است:</td>
|
||||||
|
<td><input asp-for="Suggestion.SuggestedValidDeathDate" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>آرامگاه:</td>
|
||||||
|
<td>
|
||||||
|
<select asp-for="Suggestion.SuggestedDeathLocationId">
|
||||||
|
@foreach (var location in Model.Locations)
|
||||||
|
{
|
||||||
|
<option value="@(location.Id == 0 ? "" : location.Id.ToString())">@location.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
عنوان شجرهنامه:
|
||||||
|
<p class="up-hint"><small>پُر کنید تا این شخصیت روی <a href="/people" target="_blank">صفحهٔ شجرهنامهها</a> با این عنوان فهرست شود.</small></p>
|
||||||
|
</td>
|
||||||
|
<td><input asp-for="Suggestion.SuggestedFamilyTreeCaption" size="40" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>توضیحات:</td>
|
||||||
|
<td><textarea asp-for="Suggestion.SuggestedDescription" rows="10" 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.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"])
|
||||||
|
}
|
||||||
|
}
|
||||||
156
GanjooRazor/Pages/SuggestPersonEdit.cshtml.cs
Normal file
156
GanjooRazor/Pages/SuggestPersonEdit.cshtml.cs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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 an edit to an already-approved GanjoorRelatedPerson.
|
||||||
|
/// Any logged-in user may submit here (same bar as suggesting a poem correction); nothing is
|
||||||
|
/// changed until a moderator approves it via Admin/ReviewPersonEdits. Reachable both from a
|
||||||
|
/// person's own public page (Person.cshtml) and from the poem-correction moderation page
|
||||||
|
/// (ReviewEdits.cshtml, when a correction links to an existing person).
|
||||||
|
/// </summary>
|
||||||
|
public class SuggestPersonEditModel : LoginPartialEnabledPageModel
|
||||||
|
{
|
||||||
|
public SuggestPersonEditModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LastError { get; set; }
|
||||||
|
|
||||||
|
public string LastResult { get; set; }
|
||||||
|
|
||||||
|
public GanjoorRelatedPerson Person { get; set; }
|
||||||
|
|
||||||
|
[BindProperty]
|
||||||
|
public GanjoorPersonEditSuggestion Suggestion { get; set; }
|
||||||
|
|
||||||
|
public List<GanjoorGeoLocation> Locations { get; set; }
|
||||||
|
|
||||||
|
private async Task ReadLocationsAsync()
|
||||||
|
{
|
||||||
|
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/locations");
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
LastError = await ReadErrorMessageAsync(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Locations = new List<GanjoorGeoLocation>
|
||||||
|
{
|
||||||
|
new GanjoorGeoLocation() { Id = 0, Latitude = 0, Longitude = 0, Name = "" }
|
||||||
|
};
|
||||||
|
Locations.AddRange(JsonConvert.DeserializeObject<GanjoorGeoLocation[]>(await response.Content.ReadAsStringAsync()));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillSuggestionFromCurrentPerson()
|
||||||
|
{
|
||||||
|
// the form is pre-filled with the person's CURRENT values, so submitting without
|
||||||
|
// changing anything is a no-op suggestion (harmless, just not useful) rather than
|
||||||
|
// accidentally blanking fields the contributor didn't mean to touch
|
||||||
|
Suggestion = new GanjoorPersonEditSuggestion()
|
||||||
|
{
|
||||||
|
PersonId = Person.Id,
|
||||||
|
SuggestedName = Person.Name,
|
||||||
|
SuggestedDescription = Person.Description,
|
||||||
|
SuggestedWikiUrl = Person.WikiUrl,
|
||||||
|
SuggestedBirthYearInLHijri = Person.BirthYearInLHijri,
|
||||||
|
SuggestedDeathYearInLHijri = Person.DeathYearInLHijri,
|
||||||
|
SuggestedValidBirthDate = Person.ValidBirthDate,
|
||||||
|
SuggestedValidDeathDate = Person.ValidDeathDate,
|
||||||
|
SuggestedBirthLocationId = Person.BirthLocationId,
|
||||||
|
SuggestedDeathLocationId = Person.DeathLocationId,
|
||||||
|
SuggestedFamilyTreeCaption = Person.FamilyTreeCaption,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetAsync(int id)
|
||||||
|
{
|
||||||
|
InitializeCommonPageState();
|
||||||
|
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReadLocationsAsync();
|
||||||
|
|
||||||
|
if (!await PreparePersonAsync(id))
|
||||||
|
{
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
FillSuggestionFromCurrentPerson();
|
||||||
|
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostSuggestEditAsync(int id)
|
||||||
|
{
|
||||||
|
InitializeCommonPageState();
|
||||||
|
|
||||||
|
if (!LoggedIn)
|
||||||
|
{
|
||||||
|
return Redirect($"/login?redirect={Uri.EscapeDataString(Request.Path)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
LastResult = "";
|
||||||
|
|
||||||
|
await ReadLocationsAsync();
|
||||||
|
|
||||||
|
if (!await PreparePersonAsync(id))
|
||||||
|
{
|
||||||
|
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/{id}/editsuggestion",
|
||||||
|
new StringContent(JsonConvert.SerializeObject(Suggestion), Encoding.UTF8, "application/json"));
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
LastResult = JsonConvert.DeserializeObject<string>(await response.Content.ReadAsStringAsync());
|
||||||
|
FillSuggestionFromCurrentPerson();
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
LastResult = $"پیشنهاد ویرایش شما ثبت شد و پس از بررسی توسط مدیران اعمال خواهد شد. <a role=\"button\" href=\"/person/{id}\" class=\"actionlink\">بازگشت به صفحهٔ شخصیت</a>";
|
||||||
|
|
||||||
|
FillSuggestionFromCurrentPerson();
|
||||||
|
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,25 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using RMuseum.Models.Auth.Memory;
|
||||||
using RMuseum.Models.Ganjoor;
|
using RMuseum.Models.Ganjoor;
|
||||||
using RMuseum.Models.Ganjoor.ViewModels;
|
using RMuseum.Models.Ganjoor.ViewModels;
|
||||||
using RMuseum.Services;
|
using RMuseum.Services;
|
||||||
|
using RSecurityBackend.Models.Auth.Memory;
|
||||||
|
using RSecurityBackend.Models.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace RMuseum.Controllers
|
namespace RMuseum.Controllers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// related people (family tree / person tagging) - read-only for now, see IGanjoorRelatedPersonService
|
/// related people (family tree / person tagging) - reads are anonymous; the only write paths are
|
||||||
|
/// the suggestion queue below (any logged-in user may suggest an edit to an existing person,
|
||||||
|
/// same as suggesting a poem correction; a moderator reviews it the same way a poem correction is
|
||||||
|
/// reviewed). There is no direct-edit endpoint for GanjoorRelatedPerson.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[Route("api/people")]
|
[Route("api/people")]
|
||||||
@ -101,6 +111,84 @@ namespace RMuseum.Controllers
|
|||||||
return Ok(res.Result);
|
return Ok(res.Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggest an edit to an already-approved person's own fields - any logged-in user, same as
|
||||||
|
/// suggesting a poem correction. Goes into the pending queue; does not change the person.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <param name="suggestion"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("{id:int}/editsuggestion")]
|
||||||
|
[Authorize]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonEditSuggestion))]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
|
||||||
|
public async Task<IActionResult> SuggestPersonEditAsync(int id, [FromBody] GanjoorPersonEditSuggestion suggestion)
|
||||||
|
{
|
||||||
|
suggestion.PersonId = id;
|
||||||
|
suggestion.UserId = new Guid(User.Claims.First(c => c.Type == "UserId").Value);
|
||||||
|
var res = await _personService.SuggestPersonEditAsync(suggestion);
|
||||||
|
if (!string.IsNullOrEmpty(res.ExceptionString))
|
||||||
|
return BadRequest(res.ExceptionString);
|
||||||
|
return Ok(res.Result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get the next unreviewed person-edit suggestion, for the moderator queue - same permission
|
||||||
|
/// as reviewing a poem correction
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skip"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("editsuggestions/next")]
|
||||||
|
[Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + SecurableItem.ModifyOperationShortName)]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonEditSuggestion))]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
|
||||||
|
public async Task<IActionResult> GetNextUnreviewedPersonEditSuggestionAsync(int skip = 0)
|
||||||
|
{
|
||||||
|
var res = await _personService.GetNextUnreviewedPersonEditSuggestionAsync(skip);
|
||||||
|
if (!string.IsNullOrEmpty(res.ExceptionString))
|
||||||
|
return BadRequest(res.ExceptionString);
|
||||||
|
|
||||||
|
var resCount = await _personService.GetUnreviewedPersonEditSuggestionCountAsync();
|
||||||
|
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 person-edit suggestion - same permission as
|
||||||
|
/// moderating a poem correction
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <param name="moderation"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("editsuggestions/{id:int}/moderate")]
|
||||||
|
[Authorize(Policy = RMuseumSecurableItem.GanjoorEntityShortName + ":" + SecurableItem.ModifyOperationShortName)]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonEditSuggestion))]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
|
||||||
|
public async Task<IActionResult> ModeratePersonEditSuggestionAsync(int id, [FromBody] PersonEditSuggestionModerationViewModel moderation)
|
||||||
|
{
|
||||||
|
Guid userId = new Guid(User.Claims.First(c => c.Type == "UserId").Value);
|
||||||
|
var res = await _personService.ModeratePersonEditSuggestionAsync(userId, id, moderation.Result, moderation.ReviewNote);
|
||||||
|
if (!string.IsNullOrEmpty(res.ExceptionString))
|
||||||
|
return BadRequest(res.ExceptionString);
|
||||||
|
return Ok(res.Result);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// person service
|
/// person service
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -619,6 +619,12 @@ namespace RMuseum.DbContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<GanjoorPersonAffiliation> GanjoorPersonAffiliations { get; set; }
|
public DbSet<GanjoorPersonAffiliation> GanjoorPersonAffiliations { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// pending/reviewed suggested edits to an already-approved GanjoorRelatedPerson's own fields -
|
||||||
|
/// see GanjoorPersonEditSuggestion
|
||||||
|
/// </summary>
|
||||||
|
public DbSet<GanjoorPersonEditSuggestion> GanjoorPersonEditSuggestions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Books (PDF Library)
|
/// Books (PDF Library)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
146
RMuseum/Models/Ganjoor/GanjoorPersonEditSuggestion.cs
Normal file
146
RMuseum/Models/Ganjoor/GanjoorPersonEditSuggestion.cs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
using RSecurityBackend.Models.Auth.Db;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace RMuseum.Models.Ganjoor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// a contributor's suggested edit to an existing, already-approved GanjoorRelatedPerson's own
|
||||||
|
/// fields (name, bio, birth/death info, family tree caption, ...). Reviewed/approved the same
|
||||||
|
/// way a GanjoorPoemGeoDateTagCorrection is - see GanjoorService-ModeratePersonEditSuggestion.cs -
|
||||||
|
/// but standalone: it targets a person directly, not a poem/couplet. There is no admin-only
|
||||||
|
/// direct-edit path for GanjoorRelatedPerson; this suggestion queue is the only way any of its
|
||||||
|
/// fields change after it is first created.
|
||||||
|
/// </summary>
|
||||||
|
public class GanjoorPersonEditSuggestion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// suggestion id
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the person this suggestion proposes changes for
|
||||||
|
/// </summary>
|
||||||
|
public int PersonId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the person this suggestion proposes changes for (their CURRENT, still-unchanged fields -
|
||||||
|
/// for showing a before/after diff to the moderator)
|
||||||
|
/// </summary>
|
||||||
|
public virtual GanjoorRelatedPerson Person { 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; }
|
||||||
|
|
||||||
|
// suggested full replacement state of every editable field - the contributor's form is
|
||||||
|
// pre-filled with the person's CURRENT values (see Person above), so these represent what
|
||||||
|
// the record ends up with after their edits, not a partial diff of only changed fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested name
|
||||||
|
/// </summary>
|
||||||
|
public string SuggestedName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested description
|
||||||
|
/// </summary>
|
||||||
|
public string SuggestedDescription { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested wikipedia url
|
||||||
|
/// </summary>
|
||||||
|
public string SuggestedWikiUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested birth year in lunar hijri
|
||||||
|
/// </summary>
|
||||||
|
public int SuggestedBirthYearInLHijri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested death year in lunar hijri
|
||||||
|
/// </summary>
|
||||||
|
public int SuggestedDeathYearInLHijri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested ValidBirthDate
|
||||||
|
/// </summary>
|
||||||
|
public bool SuggestedValidBirthDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested ValidDeathDate
|
||||||
|
/// </summary>
|
||||||
|
public bool SuggestedValidDeathDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested birth location id
|
||||||
|
/// </summary>
|
||||||
|
public int? SuggestedBirthLocationId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested birth location
|
||||||
|
/// </summary>
|
||||||
|
public virtual GanjoorGeoLocation SuggestedBirthLocation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested death location id
|
||||||
|
/// </summary>
|
||||||
|
public int? SuggestedDeathLocationId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested death location
|
||||||
|
/// </summary>
|
||||||
|
public virtual GanjoorGeoLocation SuggestedDeathLocation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggested family tree caption
|
||||||
|
/// </summary>
|
||||||
|
public string SuggestedFamilyTreeCaption { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// suggester's note to the moderator
|
||||||
|
/// </summary>
|
||||||
|
public string SuggestionNote { 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
namespace RMuseum.Models.Ganjoor.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// a moderator's decision on a pending GanjoorPersonEditSuggestion
|
||||||
|
/// </summary>
|
||||||
|
public class PersonEditSuggestionModerationViewModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// review result
|
||||||
|
/// </summary>
|
||||||
|
public CorrectionReviewResult Result { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// review note
|
||||||
|
/// </summary>
|
||||||
|
public string ReviewNote { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2818,7 +2818,10 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="T:RMuseum.Controllers.GanjoorRelatedPersonController">
|
<member name="T:RMuseum.Controllers.GanjoorRelatedPersonController">
|
||||||
<summary>
|
<summary>
|
||||||
related people (family tree / person tagging) - read-only for now, see IGanjoorRelatedPersonService
|
related people (family tree / person tagging) - reads are anonymous; the only write paths are
|
||||||
|
the suggestion queue below (any logged-in user may suggest an edit to an existing person,
|
||||||
|
same as suggesting a poem correction; a moderator reviews it the same way a poem correction is
|
||||||
|
reviewed). There is no direct-edit endpoint for GanjoorRelatedPerson.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetPeopleAsync">
|
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetPeopleAsync">
|
||||||
@ -2857,6 +2860,32 @@
|
|||||||
<param name="id"></param>
|
<param name="id"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.SuggestPersonEditAsync(System.Int32,RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion)">
|
||||||
|
<summary>
|
||||||
|
suggest an edit to an already-approved person's own fields - any logged-in user, same as
|
||||||
|
suggesting a poem correction. Goes into the pending queue; does not change the person.
|
||||||
|
</summary>
|
||||||
|
<param name="id"></param>
|
||||||
|
<param name="suggestion"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetNextUnreviewedPersonEditSuggestionAsync(System.Int32)">
|
||||||
|
<summary>
|
||||||
|
get the next unreviewed person-edit suggestion, for the moderator queue - same permission
|
||||||
|
as reviewing a poem correction
|
||||||
|
</summary>
|
||||||
|
<param name="skip"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.ModeratePersonEditSuggestionAsync(System.Int32,RMuseum.Models.Ganjoor.ViewModels.PersonEditSuggestionModerationViewModel)">
|
||||||
|
<summary>
|
||||||
|
apply a moderator's decision to a pending person-edit suggestion - same permission as
|
||||||
|
moderating a poem correction
|
||||||
|
</summary>
|
||||||
|
<param name="id"></param>
|
||||||
|
<param name="moderation"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
<member name="F:RMuseum.Controllers.GanjoorRelatedPersonController._personService">
|
<member name="F:RMuseum.Controllers.GanjoorRelatedPersonController._personService">
|
||||||
<summary>
|
<summary>
|
||||||
person service
|
person service
|
||||||
@ -3987,6 +4016,12 @@
|
|||||||
approved non-family ties between people (e.g. minister-to-king) - see GanjoorPersonAffiliation
|
approved non-family ties between people (e.g. minister-to-king) - see GanjoorPersonAffiliation
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:RMuseum.DbContext.RMuseumDbContext.GanjoorPersonEditSuggestions">
|
||||||
|
<summary>
|
||||||
|
pending/reviewed suggested edits to an already-approved GanjoorRelatedPerson's own fields -
|
||||||
|
see GanjoorPersonEditSuggestion
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:RMuseum.DbContext.RMuseumDbContext.Books">
|
<member name="P:RMuseum.DbContext.RMuseumDbContext.Books">
|
||||||
<summary>
|
<summary>
|
||||||
Books (PDF Library)
|
Books (PDF Library)
|
||||||
@ -9240,6 +9275,142 @@
|
|||||||
free-text note (e.g. sourcing/reasoning, or what the tie actually is when AffiliationType is Other)
|
free-text note (e.g. sourcing/reasoning, or what the tie actually is when AffiliationType is Other)
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="T:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion">
|
||||||
|
<summary>
|
||||||
|
a contributor's suggested edit to an existing, already-approved GanjoorRelatedPerson's own
|
||||||
|
fields (name, bio, birth/death info, family tree caption, ...). Reviewed/approved the same
|
||||||
|
way a GanjoorPoemGeoDateTagCorrection is - see GanjoorService-ModeratePersonEditSuggestion.cs -
|
||||||
|
but standalone: it targets a person directly, not a poem/couplet. There is no admin-only
|
||||||
|
direct-edit path for GanjoorRelatedPerson; this suggestion queue is the only way any of its
|
||||||
|
fields change after it is first created.
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.Id">
|
||||||
|
<summary>
|
||||||
|
suggestion id
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.PersonId">
|
||||||
|
<summary>
|
||||||
|
the person this suggestion proposes changes for
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.Person">
|
||||||
|
<summary>
|
||||||
|
the person this suggestion proposes changes for (their CURRENT, still-unchanged fields -
|
||||||
|
for showing a before/after diff to the moderator)
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.Date">
|
||||||
|
<summary>
|
||||||
|
date
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.UserId">
|
||||||
|
<summary>
|
||||||
|
suggester's user id
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.User">
|
||||||
|
<summary>
|
||||||
|
suggester
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedName">
|
||||||
|
<summary>
|
||||||
|
suggested name
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedDescription">
|
||||||
|
<summary>
|
||||||
|
suggested description
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedWikiUrl">
|
||||||
|
<summary>
|
||||||
|
suggested wikipedia url
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedBirthYearInLHijri">
|
||||||
|
<summary>
|
||||||
|
suggested birth year in lunar hijri
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedDeathYearInLHijri">
|
||||||
|
<summary>
|
||||||
|
suggested death year in lunar hijri
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedValidBirthDate">
|
||||||
|
<summary>
|
||||||
|
suggested ValidBirthDate
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedValidDeathDate">
|
||||||
|
<summary>
|
||||||
|
suggested ValidDeathDate
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedBirthLocationId">
|
||||||
|
<summary>
|
||||||
|
suggested birth location id
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedBirthLocation">
|
||||||
|
<summary>
|
||||||
|
suggested birth location
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedDeathLocationId">
|
||||||
|
<summary>
|
||||||
|
suggested death location id
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedDeathLocation">
|
||||||
|
<summary>
|
||||||
|
suggested death location
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestedFamilyTreeCaption">
|
||||||
|
<summary>
|
||||||
|
suggested family tree caption
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.SuggestionNote">
|
||||||
|
<summary>
|
||||||
|
suggester's note to the moderator
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.Reviewed">
|
||||||
|
<summary>
|
||||||
|
reviewed
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.Result">
|
||||||
|
<summary>
|
||||||
|
review result
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.ReviewDate">
|
||||||
|
<summary>
|
||||||
|
review date
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.ReviewNote">
|
||||||
|
<summary>
|
||||||
|
review note
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.ReviewerUserId">
|
||||||
|
<summary>
|
||||||
|
reviewer's user id
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion.ReviewerUser">
|
||||||
|
<summary>
|
||||||
|
reviewer
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="T:RMuseum.Models.Ganjoor.GanjoorPersonRelation">
|
<member name="T:RMuseum.Models.Ganjoor.GanjoorPersonRelation">
|
||||||
<summary>
|
<summary>
|
||||||
an approved kinship edge between two people (GanjoorRelatedPerson rows) - the live/materialized
|
an approved kinship edge between two people (GanjoorRelatedPerson rows) - the live/materialized
|
||||||
@ -14276,6 +14447,21 @@
|
|||||||
model (not an EF entity), so no [NotMapped] or migration concerns here.
|
model (not an EF entity), so no [NotMapped] or migration concerns here.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="T:RMuseum.Models.Ganjoor.ViewModels.PersonEditSuggestionModerationViewModel">
|
||||||
|
<summary>
|
||||||
|
a moderator's decision on a pending GanjoorPersonEditSuggestion
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.ViewModels.PersonEditSuggestionModerationViewModel.Result">
|
||||||
|
<summary>
|
||||||
|
review result
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="P:RMuseum.Models.Ganjoor.ViewModels.PersonEditSuggestionModerationViewModel.ReviewNote">
|
||||||
|
<summary>
|
||||||
|
review note
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="T:RMuseum.Models.Ganjoor.ViewModels.PersonGraphSuggestion">
|
<member name="T:RMuseum.Models.Ganjoor.ViewModels.PersonGraphSuggestion">
|
||||||
<summary>
|
<summary>
|
||||||
deserialized shape of GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson - a brand new
|
deserialized shape of GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson - a brand new
|
||||||
@ -17681,9 +17867,13 @@
|
|||||||
</member>
|
</member>
|
||||||
<member name="T:RMuseum.Services.IGanjoorRelatedPersonService">
|
<member name="T:RMuseum.Services.IGanjoorRelatedPersonService">
|
||||||
<summary>
|
<summary>
|
||||||
related people (family tree / person tagging) service - read-only for now: the only way a
|
related people (family tree / person tagging) service. A new person is normally created via
|
||||||
new person is created is via the geo/date/person tag correction's SuggestedPersonGraphJson,
|
the geo/date/person tag correction's SuggestedPersonGraphJson, materialized on moderator
|
||||||
materialized on moderator approval (see GanjoorService-ModeratePoemCorrection.cs)
|
approval (see GanjoorService-ModeratePoemCorrection.cs). Editing an already-approved person's
|
||||||
|
own fields (e.g. adding a FamilyTreeCaption after the fact) goes through the suggest/review
|
||||||
|
queue below (SuggestPersonEditAsync / ModeratePersonEditSuggestionAsync) - there is no
|
||||||
|
direct-edit path; nothing ever writes to a GanjoorRelatedPerson's fields except that approval
|
||||||
|
step and the original creation-on-approval in GanjoorService-ModeratePoemCorrection.cs.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetPeopleAsync">
|
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetPeopleAsync">
|
||||||
@ -17722,6 +17912,41 @@
|
|||||||
<param name="id"></param>
|
<param name="id"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.SuggestPersonEditAsync(RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion)">
|
||||||
|
<summary>
|
||||||
|
submit a suggested edit to an already-approved person's own fields - goes into the
|
||||||
|
pending queue, does not change the person itself until a moderator approves it
|
||||||
|
</summary>
|
||||||
|
<param name="suggestion"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetNextUnreviewedPersonEditSuggestionAsync(System.Int32)">
|
||||||
|
<summary>
|
||||||
|
get the next unreviewed person-edit suggestion (for the moderator queue), including the
|
||||||
|
target person's current fields (for a before/after diff) and the suggester's nickname
|
||||||
|
</summary>
|
||||||
|
<param name="skip"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetUnreviewedPersonEditSuggestionCountAsync">
|
||||||
|
<summary>
|
||||||
|
unreviewed person-edit suggestion count
|
||||||
|
</summary>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.ModeratePersonEditSuggestionAsync(System.Guid,System.Int32,RMuseum.Models.Ganjoor.CorrectionReviewResult,System.String)">
|
||||||
|
<summary>
|
||||||
|
apply a moderator's decision to a pending person-edit suggestion. On Approved, copies the
|
||||||
|
suggestion's Suggested* fields onto the target GanjoorRelatedPerson (Id and
|
||||||
|
MachineGenerated on the person are left untouched); any other result just marks the
|
||||||
|
suggestion reviewed/rejected without touching the person.
|
||||||
|
</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">
|
<member name="T:RMuseum.Services.IGanjoorService">
|
||||||
<summary>
|
<summary>
|
||||||
Ganjoor Poems Content Privider Service
|
Ganjoor Poems Content Privider Service
|
||||||
@ -21191,6 +21416,36 @@
|
|||||||
<param name="id"></param>
|
<param name="id"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.SuggestPersonEditAsync(RMuseum.Models.Ganjoor.GanjoorPersonEditSuggestion)">
|
||||||
|
<summary>
|
||||||
|
submit a suggested edit to an already-approved person's own fields
|
||||||
|
</summary>
|
||||||
|
<param name="suggestion"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetNextUnreviewedPersonEditSuggestionAsync(System.Int32)">
|
||||||
|
<summary>
|
||||||
|
get the next unreviewed person-edit suggestion for the moderator queue
|
||||||
|
</summary>
|
||||||
|
<param name="skip"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetUnreviewedPersonEditSuggestionCountAsync">
|
||||||
|
<summary>
|
||||||
|
unreviewed person-edit suggestion count
|
||||||
|
</summary>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.ModeratePersonEditSuggestionAsync(System.Guid,System.Int32,RMuseum.Models.Ganjoor.CorrectionReviewResult,System.String)">
|
||||||
|
<summary>
|
||||||
|
apply a moderator's decision to a pending person-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">
|
<member name="F:RMuseum.Services.Implementation.GanjoorRelatedPersonService._context">
|
||||||
<summary>
|
<summary>
|
||||||
Database Context
|
Database Context
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
using RMuseum.Models.Ganjoor;
|
using RMuseum.Models.Ganjoor;
|
||||||
using RMuseum.Models.Ganjoor.ViewModels;
|
using RMuseum.Models.Ganjoor.ViewModels;
|
||||||
using RSecurityBackend.Models.Generic;
|
using RSecurityBackend.Models.Generic;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace RMuseum.Services
|
namespace RMuseum.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// related people (family tree / person tagging) service - read-only for now: the only way a
|
/// related people (family tree / person tagging) service. A new person is normally created via
|
||||||
/// new person is created is via the geo/date/person tag correction's SuggestedPersonGraphJson,
|
/// the geo/date/person tag correction's SuggestedPersonGraphJson, materialized on moderator
|
||||||
/// materialized on moderator approval (see GanjoorService-ModeratePoemCorrection.cs)
|
/// approval (see GanjoorService-ModeratePoemCorrection.cs). Editing an already-approved person's
|
||||||
|
/// own fields (e.g. adding a FamilyTreeCaption after the fact) goes through the suggest/review
|
||||||
|
/// queue below (SuggestPersonEditAsync / ModeratePersonEditSuggestionAsync) - there is no
|
||||||
|
/// direct-edit path; nothing ever writes to a GanjoorRelatedPerson's fields except that approval
|
||||||
|
/// step and the original creation-on-approval in GanjoorService-ModeratePoemCorrection.cs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IGanjoorRelatedPersonService
|
public interface IGanjoorRelatedPersonService
|
||||||
{
|
{
|
||||||
@ -47,5 +52,40 @@ namespace RMuseum.Services
|
|||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<RServiceResult<PoemGeoDateTag[]>> GetPoemsByPersonAsync(int id);
|
Task<RServiceResult<PoemGeoDateTag[]>> GetPoemsByPersonAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// submit a suggested edit to an already-approved person's own fields - goes into the
|
||||||
|
/// pending queue, does not change the person itself until a moderator approves it
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="suggestion"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<RServiceResult<GanjoorPersonEditSuggestion>> SuggestPersonEditAsync(GanjoorPersonEditSuggestion suggestion);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get the next unreviewed person-edit suggestion (for the moderator queue), including the
|
||||||
|
/// target person's current fields (for a before/after diff) and the suggester's nickname
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skip"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<RServiceResult<GanjoorPersonEditSuggestion>> GetNextUnreviewedPersonEditSuggestionAsync(int skip);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// unreviewed person-edit suggestion count
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<RServiceResult<int>> GetUnreviewedPersonEditSuggestionCountAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// apply a moderator's decision to a pending person-edit suggestion. On Approved, copies the
|
||||||
|
/// suggestion's Suggested* fields onto the target GanjoorRelatedPerson (Id and
|
||||||
|
/// MachineGenerated on the person are left untouched); any other result just marks the
|
||||||
|
/// suggestion reviewed/rejected without touching the person.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moderatorUserId"></param>
|
||||||
|
/// <param name="suggestionId"></param>
|
||||||
|
/// <param name="result"></param>
|
||||||
|
/// <param name="reviewNote"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<RServiceResult<GanjoorPersonEditSuggestion>> ModeratePersonEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -172,6 +172,151 @@ namespace RMuseum.Services.Implementation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// submit a suggested edit to an already-approved person's own fields
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="suggestion"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RServiceResult<GanjoorPersonEditSuggestion>> SuggestPersonEditAsync(GanjoorPersonEditSuggestion suggestion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (suggestion == null || string.IsNullOrWhiteSpace(suggestion.SuggestedName))
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, "نام شخصیت نمیتواند خالی باشد.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var personExists = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).AnyAsync();
|
||||||
|
if (!personExists)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, "شخصیت پیدا نشد.");
|
||||||
|
}
|
||||||
|
|
||||||
|
suggestion.Id = 0;
|
||||||
|
suggestion.Date = DateTime.Now;
|
||||||
|
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();
|
||||||
|
suggestion.Reviewed = false;
|
||||||
|
suggestion.Result = CorrectionReviewResult.NotReviewed;
|
||||||
|
suggestion.ReviewNote = null;
|
||||||
|
suggestion.ReviewerUserId = null;
|
||||||
|
|
||||||
|
_context.GanjoorPersonEditSuggestions.Add(suggestion);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(suggestion);
|
||||||
|
}
|
||||||
|
catch (Exception exp)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, exp.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// get the next unreviewed person-edit suggestion for the moderator queue
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skip"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RServiceResult<GanjoorPersonEditSuggestion>> GetNextUnreviewedPersonEditSuggestionAsync(int skip)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var suggestion = await _context.GanjoorPersonEditSuggestions
|
||||||
|
.Include(s => s.Person)
|
||||||
|
.Include(s => s.User)
|
||||||
|
.Include(s => s.SuggestedBirthLocation)
|
||||||
|
.Include(s => s.SuggestedDeathLocation)
|
||||||
|
.Where(s => s.Reviewed == false)
|
||||||
|
.OrderBy(s => s.Id)
|
||||||
|
.Skip(skip)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(suggestion);
|
||||||
|
}
|
||||||
|
catch (Exception exp)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, exp.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// unreviewed person-edit suggestion count
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RServiceResult<int>> GetUnreviewedPersonEditSuggestionCountAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new RServiceResult<int>(await _context.GanjoorPersonEditSuggestions.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 person-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<GanjoorPersonEditSuggestion>> ModeratePersonEditSuggestionAsync(Guid moderatorUserId, int suggestionId, CorrectionReviewResult result, string reviewNote)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var suggestion = await _context.GanjoorPersonEditSuggestions.Where(s => s.Id == suggestionId).SingleOrDefaultAsync();
|
||||||
|
if (suggestion == null)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, "پیشنهاد ویرایش پیدا نشد.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestion.Reviewed)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, "این پیشنهاد پیشتر بررسی شده است.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == CorrectionReviewResult.Approved)
|
||||||
|
{
|
||||||
|
var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == suggestion.PersonId).SingleOrDefaultAsync();
|
||||||
|
if (person == null)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, "شخصیت مقصد این پیشنهاد پیدا نشد.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
suggestion.Result = result;
|
||||||
|
suggestion.ReviewNote = reviewNote;
|
||||||
|
suggestion.ReviewDate = DateTime.Now;
|
||||||
|
suggestion.ReviewerUserId = moderatorUserId;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(suggestion);
|
||||||
|
}
|
||||||
|
catch (Exception exp)
|
||||||
|
{
|
||||||
|
return new RServiceResult<GanjoorPersonEditSuggestion>(null, exp.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Database Context
|
/// Database Context
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user