people tags #387
This commit is contained in:
parent
20b13cd0e7
commit
1c65da5ade
@ -958,6 +958,7 @@ else
|
||||
@if (geoTag.PersonId != null)
|
||||
{
|
||||
<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)
|
||||
{
|
||||
|
||||
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="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))
|
||||
{
|
||||
|
||||
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.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using RMuseum.Models.Auth.Memory;
|
||||
using RMuseum.Models.Ganjoor;
|
||||
using RMuseum.Models.Ganjoor.ViewModels;
|
||||
using RMuseum.Services;
|
||||
using RSecurityBackend.Models.Auth.Memory;
|
||||
using RSecurityBackend.Models.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RMuseum.Controllers
|
||||
{
|
||||
/// <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>
|
||||
[Produces("application/json")]
|
||||
[Route("api/people")]
|
||||
@ -101,6 +111,84 @@ namespace RMuseum.Controllers
|
||||
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>
|
||||
/// person service
|
||||
/// </summary>
|
||||
|
||||
@ -619,6 +619,12 @@ namespace RMuseum.DbContext
|
||||
/// </summary>
|
||||
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>
|
||||
/// Books (PDF Library)
|
||||
/// </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 name="T:RMuseum.Controllers.GanjoorRelatedPersonController">
|
||||
<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>
|
||||
</member>
|
||||
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetPeopleAsync">
|
||||
@ -2857,6 +2860,32 @@
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</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">
|
||||
<summary>
|
||||
person service
|
||||
@ -3987,6 +4016,12 @@
|
||||
approved non-family ties between people (e.g. minister-to-king) - see GanjoorPersonAffiliation
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
Books (PDF Library)
|
||||
@ -9240,6 +9275,142 @@
|
||||
free-text note (e.g. sourcing/reasoning, or what the tie actually is when AffiliationType is Other)
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
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.
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
deserialized shape of GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson - a brand new
|
||||
@ -17681,9 +17867,13 @@
|
||||
</member>
|
||||
<member name="T:RMuseum.Services.IGanjoorRelatedPersonService">
|
||||
<summary>
|
||||
related people (family tree / person tagging) service - read-only for now: the only way a
|
||||
new person is created is via the geo/date/person tag correction's SuggestedPersonGraphJson,
|
||||
materialized on moderator approval (see GanjoorService-ModeratePoemCorrection.cs)
|
||||
related people (family tree / person tagging) service. A new person is normally created via
|
||||
the geo/date/person tag correction's SuggestedPersonGraphJson, materialized on moderator
|
||||
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>
|
||||
</member>
|
||||
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetPeopleAsync">
|
||||
@ -17722,6 +17912,41 @@
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</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">
|
||||
<summary>
|
||||
Ganjoor Poems Content Privider Service
|
||||
@ -21191,6 +21416,36 @@
|
||||
<param name="id"></param>
|
||||
<returns></returns>
|
||||
</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">
|
||||
<summary>
|
||||
Database Context
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
using RMuseum.Models.Ganjoor;
|
||||
using RMuseum.Models.Ganjoor.ViewModels;
|
||||
using RSecurityBackend.Models.Generic;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RMuseum.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// related people (family tree / person tagging) service - read-only for now: the only way a
|
||||
/// new person is created is via the geo/date/person tag correction's SuggestedPersonGraphJson,
|
||||
/// materialized on moderator approval (see GanjoorService-ModeratePoemCorrection.cs)
|
||||
/// related people (family tree / person tagging) service. A new person is normally created via
|
||||
/// the geo/date/person tag correction's SuggestedPersonGraphJson, materialized on moderator
|
||||
/// 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>
|
||||
public interface IGanjoorRelatedPersonService
|
||||
{
|
||||
@ -47,5 +52,40 @@ namespace RMuseum.Services
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
/// Database Context
|
||||
/// </summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user