people tags #387

This commit is contained in:
Hamid Reza Mohammadi 2026-09-28 14:41:47 +03:30
parent 5fcf862862
commit 8110e09a07
9 changed files with 849 additions and 1 deletions

View File

@ -0,0 +1,57 @@
@page
@model GanjooRazor.Pages.PeopleModel
@{
Layout = "_Layout";
}
@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">&nbsp;</div>
<div class="poem">
<h1>شجره‌نامه‌ها</h1>
@if (Model.FamilyTreeRoots == null || Model.FamilyTreeRoots.Count == 0)
{
<p><small>هنوز شجره‌نامه‌ای ثبت نشده است.</small></p>
}
else
{
<ul>
@foreach (var root in Model.FamilyTreeRoots)
{
<li>
<a href="/person/@root.Id">@root.FamilyTreeCaption</a>
<small>(@root.Name)</small>
</li>
}
</ul>
}
</div>
<div class="spacer">&nbsp;</div>
</div>
<partial name="_FooterPartial" model=@(new _FooterPartialModel(){StickyEnabled = true}) />
</div>
@if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString()))
{
@Html.Raw(ViewData["TrackingScript"])
}
}

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using RMuseum.Models.Ganjoor;
namespace GanjooRazor.Pages
{
/// <summary>
/// entry point for browsing family trees - lists every person that captions a tree
/// (GanjoorRelatedPerson.FamilyTreeCaption not empty), grouped under that caption. A person
/// with no caption of their own still shows up via their relatives on that person's own page
/// (see Person.cshtml) - this index is only for jumping straight to a named tree's root(s).
/// </summary>
public class PeopleModel : LoginPartialEnabledPageModel
{
public PeopleModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration)
{
}
public string LastError { get; set; }
public List<GanjoorRelatedPerson> FamilyTreeRoots { get; set; }
public async Task<IActionResult> OnGetAsync()
{
InitializeCommonPageState();
ViewData["Title"] = "گنجور » شجره‌نامه‌ها";
var response = await _httpClient.GetAsync($"{APIRoot.Url}/api/people/familytrees");
if (!response.IsSuccessStatusCode)
{
LastError = await ReadErrorMessageAsync(response);
return Page();
}
FamilyTreeRoots = JsonConvert.DeserializeObject<List<GanjoorRelatedPerson>>(await response.Content.ReadAsStringAsync());
return Page();
}
}
}

View File

@ -0,0 +1,135 @@
@page "{id:int}"
@model GanjooRazor.Pages.PersonModel
@using DNTPersianUtils.Core
@{
Layout = "_Layout";
}
@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">&nbsp;</div>
<div class="poem">
<h1>@Model.Person.Name</h1>
@if (!string.IsNullOrEmpty(Model.Person.FamilyTreeCaption))
{
<p><strong>شجره‌نامه: @Model.Person.FamilyTreeCaption</strong></p>
}
@if (Model.Person.BirthYearInLHijri != 0 || Model.Person.DeathYearInLHijri != 0)
{
<p>
@if (Model.Person.BirthYearInLHijri != 0)
{
<text>تولد: @Model.Person.BirthYearInLHijri.ToPersianNumbers() (قمری) @(Model.Person.ValidBirthDate ? "" : "- تخمینی")</text>
}
@if (Model.Person.DeathYearInLHijri != 0)
{
<text> — وفات: @Model.Person.DeathYearInLHijri.ToPersianNumbers() (قمری) @(Model.Person.ValidDeathDate ? "" : "- تخمینی")</text>
}
</p>
}
@if (!string.IsNullOrEmpty(Model.Person.Description))
{
<p>@Model.Person.Description</p>
}
@if (!string.IsNullOrEmpty(Model.Person.WikiUrl))
{
<p><a href="@Model.Person.WikiUrl" target="_blank" rel="noopener">صفحهٔ ویکی‌پدیا</a></p>
}
</div>
@if (Model.RelationRows.Count > 0)
{
<div class="spacer">&nbsp;</div>
<div class="poem">
<h2>خویشاوندان</h2>
<ul>
@foreach (var row in Model.RelationRows)
{
<li>
@row.Label — <a href="/person/@row.OtherPersonId">@row.OtherPersonName</a>
@if (!string.IsNullOrEmpty(row.Note))
{
<text> (@row.Note)</text>
}
</li>
}
</ul>
</div>
}
@if (Model.AffiliationRows.Count > 0)
{
<div class="spacer">&nbsp;</div>
<div class="poem">
<h2>وابستگی‌های غیرخویشاوندی</h2>
<ul>
@foreach (var row in Model.AffiliationRows)
{
<li>
@row.SentenceBeforeOtherName<a href="/person/@row.OtherPersonId">@row.OtherPersonName</a>@row.SentenceAfterOtherName
@if (!string.IsNullOrEmpty(row.Note))
{
<text> (@row.Note)</text>
}
</li>
}
</ul>
</div>
}
@if (Model.Poems != null && Model.Poems.Length > 0)
{
<div class="spacer">&nbsp;</div>
<div class="poem">
<h2>اشعار برچسب‌خورده با این فرد</h2>
<ul>
@foreach (var tag in Model.Poems)
{
if (tag.Poem != null)
{
<li><a href="@tag.Poem.FullUrl">@tag.Poem.FullTitle</a></li>
}
}
</ul>
</div>
}
else
{
<div class="spacer">&nbsp;</div>
<div class="poem">
<p><small>هنوز شعری با این فرد برچسب‌گذاری نشده است.</small></p>
</div>
}
<div class="spacer">&nbsp;</div>
</div>
<partial name="_FooterPartial" model=@(new _FooterPartialModel(){StickyEnabled = true}) />
</div>
@if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString()))
{
@Html.Raw(ViewData["TrackingScript"])
}
}

View File

@ -0,0 +1,166 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using RMuseum.Models.Ganjoor;
using RMuseum.Models.Ganjoor.ViewModels;
namespace GanjooRazor.Pages
{
/// <summary>
/// read-only person/family-tree browsing page - shows one GanjoorRelatedPerson, their kinship
/// and non-family ties (see GanjoorPersonRelation/GanjoorPersonAffiliation), and the poems
/// tagged with them. Public counterpart of the Editor.cshtml suggestion form and
/// ReviewEdits.cshtml moderation view for the same data, once it's approved and materialized.
/// </summary>
public class PersonModel : LoginPartialEnabledPageModel
{
public PersonModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration)
{
}
public string LastError { get; set; }
public GanjoorRelatedPerson Person { get; set; }
/// <summary>
/// one row per kinship edge, already resolved into display-ready text so the .cshtml
/// doesn't need to know the directional meaning of PersonRelationType
/// </summary>
public List<PersonRelationDisplayRow> RelationRows { get; set; } = new List<PersonRelationDisplayRow>();
/// <summary>
/// one row per non-family tie, same purpose as RelationRows
/// </summary>
public List<PersonAffiliationDisplayRow> AffiliationRows { get; set; } = new List<PersonAffiliationDisplayRow>();
public PoemGeoDateTag[] Poems { get; set; }
public class PersonRelationDisplayRow
{
/// <summary>
/// the other person's role relative to the subject (e.g. "فرزند", "پدر/مادر", "نیا")
/// </summary>
public string Label { get; set; }
public int OtherPersonId { get; set; }
public string OtherPersonName { get; set; }
public string Note { get; set; }
}
public class PersonAffiliationDisplayRow
{
/// <summary>
/// full Persian sentence with a "{0}" placeholder for where the other person's linked
/// name goes (kept as a placeholder, rather than a pre-built string, so the .cshtml can
/// still render the name as a link)
/// </summary>
public string SentenceBeforeOtherName { get; set; }
public string SentenceAfterOtherName { get; set; }
public int OtherPersonId { get; set; }
public string OtherPersonName { get; set; }
public string Note { get; set; }
}
private static string RelationLabel(GanjoorPersonRelationInfo r)
{
switch (r.RelationType)
{
case PersonRelationType.Parent:
return r.SubjectIsPerson1 ? "فرزند" : "پدر/مادر";
case PersonRelationType.Sibling:
return "خواهر/برادر";
case PersonRelationType.Spouse:
return "همسر";
case PersonRelationType.Ancestor:
var word = r.SubjectIsPerson1 ? "نواده" : "نیا";
return r.DegreeHint != null ? $"{word} (فاصلهٔ {r.DegreeHint} نسل)" : word;
default:
return r.RelationType.ToString();
}
}
// Persian word for the subordinate/serving-role side of each directional affiliation type -
// used to build a full sentence below, since (unlike family relations) there's no natural
// short label for "the person this one served"
private static readonly System.Collections.Generic.Dictionary<PersonAffiliationType, string> _affiliationRoleWords =
new System.Collections.Generic.Dictionary<PersonAffiliationType, string>()
{
{ PersonAffiliationType.Minister, "وزیرِ" },
{ PersonAffiliationType.Advisor, "مشاورِ" },
{ PersonAffiliationType.Courtier, "درباریِ" },
{ PersonAffiliationType.Patron, "حامیِ" },
{ PersonAffiliationType.Ally, "متحدِ" },
{ PersonAffiliationType.Rival, "رقیبِ" },
{ PersonAffiliationType.Other, "دارای نسبتی (به یادداشت نگاه کنید) با" },
};
private static bool IsSymmetricAffiliation(PersonAffiliationType t) =>
t == PersonAffiliationType.Ally || t == PersonAffiliationType.Rival || t == PersonAffiliationType.Other;
public async Task<IActionResult> OnGetAsync(int id)
{
InitializeCommonPageState();
var personResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/people/{id}");
if (!personResponse.IsSuccessStatusCode)
{
LastError = await ReadErrorMessageAsync(personResponse);
return Page();
}
Person = JsonConvert.DeserializeObject<GanjoorRelatedPerson>(await personResponse.Content.ReadAsStringAsync());
if (Person == null)
{
LastError = "فردی با این کد پیدا نشد.";
return Page();
}
ViewData["Title"] = $"گنجور » {Person.Name}";
var relationsResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/people/{id}/relations");
if (!relationsResponse.IsSuccessStatusCode)
{
LastError = await ReadErrorMessageAsync(relationsResponse);
return Page();
}
var relationsData = JsonConvert.DeserializeObject<GanjoorPersonRelationsViewModel>(await relationsResponse.Content.ReadAsStringAsync());
foreach (var r in relationsData.Relations ?? new List<GanjoorPersonRelationInfo>())
{
RelationRows.Add(new PersonRelationDisplayRow()
{
Label = RelationLabel(r),
OtherPersonId = r.OtherPersonId,
OtherPersonName = r.OtherPersonName,
Note = r.Note,
});
}
foreach (var a in relationsData.Affiliations ?? new List<GanjoorPersonAffiliationInfo>())
{
string roleWord = _affiliationRoleWords.TryGetValue(a.AffiliationType, out var w) ? w : a.AffiliationType.ToString();
bool subjectServes = a.SubjectIsPerson1 || IsSymmetricAffiliation(a.AffiliationType);
AffiliationRows.Add(new PersonAffiliationDisplayRow()
{
SentenceBeforeOtherName = subjectServes ? $"این فرد {roleWord} " : "",
SentenceAfterOtherName = subjectServes ? " بود" : $" {roleWord} این فرد بود",
OtherPersonId = a.OtherPersonId,
OtherPersonName = a.OtherPersonName,
Note = a.Note,
});
}
var poemsResponse = await _httpClient.GetAsync($"{APIRoot.Url}/api/people/{id}/poems");
if (!poemsResponse.IsSuccessStatusCode)
{
LastError = await ReadErrorMessageAsync(poemsResponse);
return Page();
}
Poems = JsonConvert.DeserializeObject<PoemGeoDateTag[]>(await poemsResponse.Content.ReadAsStringAsync());
return Page();
}
}
}

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RMuseum.Models.Ganjoor;
using RMuseum.Models.Ganjoor.ViewModels;
using RMuseum.Services;
using System.Net;
using System.Threading.Tasks;
@ -30,12 +31,30 @@ namespace RMuseum.Controllers
return Ok(res.Result);
}
/// <summary>
/// get people who caption a family tree - the entry points for browsing family trees.
/// Registered before the "{id}" route below (and constrained to int there) so this literal
/// segment isn't swallowed as an id.
/// </summary>
/// <returns></returns>
[HttpGet("familytrees")]
[AllowAnonymous]
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorRelatedPerson[]))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
public async Task<IActionResult> GetFamilyTreeRootsAsync()
{
var res = await _personService.GetFamilyTreeRootsAsync();
if (!string.IsNullOrEmpty(res.ExceptionString))
return BadRequest(res.ExceptionString);
return Ok(res.Result);
}
/// <summary>
/// get person by id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
[HttpGet("{id:int}")]
[AllowAnonymous]
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorRelatedPerson))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
@ -47,6 +66,41 @@ namespace RMuseum.Controllers
return Ok(res.Result);
}
/// <summary>
/// get a person along with all their kinship/affiliation edges, for the read-only
/// person/family-tree browsing page
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id:int}/relations")]
[AllowAnonymous]
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelationsViewModel))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
public async Task<IActionResult> GetPersonRelationsAsync(int id)
{
var res = await _personService.GetPersonRelationsAsync(id);
if (!string.IsNullOrEmpty(res.ExceptionString))
return BadRequest(res.ExceptionString);
return Ok(res.Result);
}
/// <summary>
/// get the poems tagged with this person
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id:int}/poems")]
[AllowAnonymous]
[ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(PoemGeoDateTag[]))]
[ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))]
public async Task<IActionResult> GetPoemsByPersonAsync(int id)
{
var res = await _personService.GetPoemsByPersonAsync(id);
if (!string.IsNullOrEmpty(res.ExceptionString))
return BadRequest(res.ExceptionString);
return Ok(res.Result);
}
/// <summary>
/// person service
/// </summary>

View File

@ -0,0 +1,96 @@
using System.Collections.Generic;
namespace RMuseum.Models.Ganjoor.ViewModels
{
/// <summary>
/// a person plus their kinship/affiliation edges, resolved with the other side's name - the
/// read-only counterpart of the pending-suggestion PersonGraphSuggestion (see
/// GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson), served by GET api/people/{id}/relations
/// </summary>
public class GanjoorPersonRelationsViewModel
{
/// <summary>
/// the subject person
/// </summary>
public GanjoorRelatedPerson Person { get; set; }
/// <summary>
/// kinship edges touching Person (either side)
/// </summary>
public List<GanjoorPersonRelationInfo> Relations { get; set; }
/// <summary>
/// non-family ties touching Person (either side)
/// </summary>
public List<GanjoorPersonAffiliationInfo> Affiliations { get; set; }
}
/// <summary>
/// one GanjoorPersonRelation row, resolved from Person's point of view
/// </summary>
public class GanjoorPersonRelationInfo
{
/// <summary>
/// the other person in this relation
/// </summary>
public int OtherPersonId { get; set; }
/// <summary>
/// the other person's name (denormalized here so the client doesn't need a second lookup)
/// </summary>
public string OtherPersonName { get; set; }
/// <summary>
/// the kind of kinship edge
/// </summary>
public PersonRelationType RelationType { get; set; }
/// <summary>
/// see GanjoorPersonRelation.DegreeHint
/// </summary>
public int? DegreeHint { get; set; }
/// <summary>
/// free-text note
/// </summary>
public string Note { get; set; }
/// <summary>
/// true if the subject person is Person1 in the underlying GanjoorPersonRelation row -
/// needed by the client to render directional types (Parent, Ancestor) correctly
/// </summary>
public bool SubjectIsPerson1 { get; set; }
}
/// <summary>
/// one GanjoorPersonAffiliation row, resolved from Person's point of view - same shape/purpose
/// as GanjoorPersonRelationInfo above, but for a non-family tie
/// </summary>
public class GanjoorPersonAffiliationInfo
{
/// <summary>
/// the other person in this tie
/// </summary>
public int OtherPersonId { get; set; }
/// <summary>
/// the other person's name
/// </summary>
public string OtherPersonName { get; set; }
/// <summary>
/// the kind of non-family tie
/// </summary>
public PersonAffiliationType AffiliationType { get; set; }
/// <summary>
/// free-text note
/// </summary>
public string Note { get; set; }
/// <summary>
/// true if the subject person is Person1 in the underlying GanjoorPersonAffiliation row
/// </summary>
public bool SubjectIsPerson1 { get; set; }
}
}

View File

@ -2827,6 +2827,14 @@
</summary>
<returns></returns>
</member>
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetFamilyTreeRootsAsync">
<summary>
get people who caption a family tree - the entry points for browsing family trees.
Registered before the "{id}" route below (and constrained to int there) so this literal
segment isn't swallowed as an id.
</summary>
<returns></returns>
</member>
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetPersonAsync(System.Int32)">
<summary>
get person by id
@ -2834,6 +2842,21 @@
<param name="id"></param>
<returns></returns>
</member>
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetPersonRelationsAsync(System.Int32)">
<summary>
get a person along with all their kinship/affiliation edges, for the read-only
person/family-tree browsing page
</summary>
<param name="id"></param>
<returns></returns>
</member>
<member name="M:RMuseum.Controllers.GanjoorRelatedPersonController.GetPoemsByPersonAsync(System.Int32)">
<summary>
get the poems tagged with this person
</summary>
<param name="id"></param>
<returns></returns>
</member>
<member name="F:RMuseum.Controllers.GanjoorRelatedPersonController._personService">
<summary>
person service
@ -12843,6 +12866,95 @@
FullUrl
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationsViewModel">
<summary>
a person plus their kinship/affiliation edges, resolved with the other side's name - the
read-only counterpart of the pending-suggestion PersonGraphSuggestion (see
GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson), served by GET api/people/{id}/relations
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationsViewModel.Person">
<summary>
the subject person
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationsViewModel.Relations">
<summary>
kinship edges touching Person (either side)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationsViewModel.Affiliations">
<summary>
non-family ties touching Person (either side)
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo">
<summary>
one GanjoorPersonRelation row, resolved from Person's point of view
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.OtherPersonId">
<summary>
the other person in this relation
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.OtherPersonName">
<summary>
the other person's name (denormalized here so the client doesn't need a second lookup)
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.RelationType">
<summary>
the kind of kinship edge
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.DegreeHint">
<summary>
see GanjoorPersonRelation.DegreeHint
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.Note">
<summary>
free-text note
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonRelationInfo.SubjectIsPerson1">
<summary>
true if the subject person is Person1 in the underlying GanjoorPersonRelation row -
needed by the client to render directional types (Parent, Ancestor) correctly
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonAffiliationInfo">
<summary>
one GanjoorPersonAffiliation row, resolved from Person's point of view - same shape/purpose
as GanjoorPersonRelationInfo above, but for a non-family tie
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonAffiliationInfo.OtherPersonId">
<summary>
the other person in this tie
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonAffiliationInfo.OtherPersonName">
<summary>
the other person's name
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonAffiliationInfo.AffiliationType">
<summary>
the kind of non-family tie
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonAffiliationInfo.Note">
<summary>
free-text note
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.ViewModels.GanjoorPersonAffiliationInfo.SubjectIsPerson1">
<summary>
true if the subject person is Person1 in the underlying GanjoorPersonAffiliation row
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.ViewModels.GanjoorPoemCompleteViewModel">
<summary>
a more complete GanjoorPoem View Model
@ -17587,6 +17699,29 @@
<param name="id"></param>
<returns></returns>
</member>
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetFamilyTreeRootsAsync">
<summary>
get people who caption a family tree (GanjoorRelatedPerson.FamilyTreeCaption not empty) -
used as the entry points for browsing family trees
</summary>
<returns></returns>
</member>
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetPersonRelationsAsync(System.Int32)">
<summary>
get a person along with all their kinship/affiliation edges (resolved with the other
side's name), for the read-only person/family-tree browsing page
</summary>
<param name="id"></param>
<returns></returns>
</member>
<member name="M:RMuseum.Services.IGanjoorRelatedPersonService.GetPoemsByPersonAsync(System.Int32)">
<summary>
get the (approved, materialized) poem geo/date tags that name this person, each carrying
enough of its Poem to link to it
</summary>
<param name="id"></param>
<returns></returns>
</member>
<member name="T:RMuseum.Services.IGanjoorService">
<summary>
Ganjoor Poems Content Privider Service
@ -21036,6 +21171,26 @@
<param name="id"></param>
<returns></returns>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetFamilyTreeRootsAsync">
<summary>
get people who caption a family tree
</summary>
<returns></returns>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetPersonRelationsAsync(System.Int32)">
<summary>
get a person along with all their kinship/affiliation edges
</summary>
<param name="id"></param>
<returns></returns>
</member>
<member name="M:RMuseum.Services.Implementation.GanjoorRelatedPersonService.GetPoemsByPersonAsync(System.Int32)">
<summary>
get the (approved, materialized) poem geo/date tags that name this person
</summary>
<param name="id"></param>
<returns></returns>
</member>
<member name="F:RMuseum.Services.Implementation.GanjoorRelatedPersonService._context">
<summary>
Database Context

View File

@ -1,4 +1,5 @@
using RMuseum.Models.Ganjoor;
using RMuseum.Models.Ganjoor.ViewModels;
using RSecurityBackend.Models.Generic;
using System.Threading.Tasks;
@ -23,5 +24,28 @@ namespace RMuseum.Services
/// <param name="id"></param>
/// <returns></returns>
Task<RServiceResult<GanjoorRelatedPerson>> GetPersonAsync(int id);
/// <summary>
/// get people who caption a family tree (GanjoorRelatedPerson.FamilyTreeCaption not empty) -
/// used as the entry points for browsing family trees
/// </summary>
/// <returns></returns>
Task<RServiceResult<GanjoorRelatedPerson[]>> GetFamilyTreeRootsAsync();
/// <summary>
/// get a person along with all their kinship/affiliation edges (resolved with the other
/// side's name), for the read-only person/family-tree browsing page
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<RServiceResult<GanjoorPersonRelationsViewModel>> GetPersonRelationsAsync(int id);
/// <summary>
/// get the (approved, materialized) poem geo/date tags that name this person, each carrying
/// enough of its Poem to link to it
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<RServiceResult<PoemGeoDateTag[]>> GetPoemsByPersonAsync(int id);
}
}

View File

@ -1,8 +1,10 @@
using Microsoft.EntityFrameworkCore;
using RMuseum.DbContext;
using RMuseum.Models.Ganjoor;
using RMuseum.Models.Ganjoor.ViewModels;
using RSecurityBackend.Models.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -55,6 +57,121 @@ namespace RMuseum.Services.Implementation
}
}
/// <summary>
/// get people who caption a family tree
/// </summary>
/// <returns></returns>
public async Task<RServiceResult<GanjoorRelatedPerson[]>> GetFamilyTreeRootsAsync()
{
try
{
return new RServiceResult<GanjoorRelatedPerson[]>
(
await _context.GanjoorRelatedPersons
.Where(p => !string.IsNullOrEmpty(p.FamilyTreeCaption))
.OrderBy(p => p.FamilyTreeCaption)
.ToArrayAsync()
);
}
catch (Exception exp)
{
return new RServiceResult<GanjoorRelatedPerson[]>(null, exp.ToString());
}
}
/// <summary>
/// get a person along with all their kinship/affiliation edges
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<RServiceResult<GanjoorPersonRelationsViewModel>> GetPersonRelationsAsync(int id)
{
try
{
var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == id).SingleOrDefaultAsync();
if (person == null)
{
return new RServiceResult<GanjoorPersonRelationsViewModel>(null, "فرد پیدا نشد.");
}
var relationRows = await _context.GanjoorPersonRelations
.Include(r => r.Person1)
.Include(r => r.Person2)
.Where(r => r.Person1Id == id || r.Person2Id == id)
.ToListAsync();
var relations = relationRows.Select(r => new GanjoorPersonRelationInfo()
{
OtherPersonId = r.Person1Id == id ? r.Person2Id : r.Person1Id,
OtherPersonName = r.Person1Id == id ? r.Person2.Name : r.Person1.Name,
RelationType = r.RelationType,
DegreeHint = r.DegreeHint,
Note = r.Note,
SubjectIsPerson1 = r.Person1Id == id,
}).ToList();
var affiliationRows = await _context.GanjoorPersonAffiliations
.Include(a => a.Person1)
.Include(a => a.Person2)
.Where(a => a.Person1Id == id || a.Person2Id == id)
.ToListAsync();
var affiliations = affiliationRows.Select(a => new GanjoorPersonAffiliationInfo()
{
OtherPersonId = a.Person1Id == id ? a.Person2Id : a.Person1Id,
OtherPersonName = a.Person1Id == id ? a.Person2.Name : a.Person1.Name,
AffiliationType = a.AffiliationType,
Note = a.Note,
SubjectIsPerson1 = a.Person1Id == id,
}).ToList();
return new RServiceResult<GanjoorPersonRelationsViewModel>(new GanjoorPersonRelationsViewModel()
{
Person = person,
Relations = relations,
Affiliations = affiliations,
});
}
catch (Exception exp)
{
return new RServiceResult<GanjoorPersonRelationsViewModel>(null, exp.ToString());
}
}
/// <summary>
/// get the (approved, materialized) poem geo/date tags that name this person
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<RServiceResult<PoemGeoDateTag[]>> GetPoemsByPersonAsync(int id)
{
try
{
var tags = await _context.PoemGeoDateTags
.Include(t => t.Poem)
.Where(t => t.PersonId == id && t.MachineGenerated == false)
.OrderBy(t => t.Id)
.ToArrayAsync();
// Poem is included only to get to FullUrl/FullTitle for a link - strip the heavy
// text fields before this goes over the wire, same as GetCatPoemGeoDateTagsAsync does
foreach (var tag in tags)
{
if (tag.Poem != null)
{
tag.Poem.HtmlText = null;
tag.Poem.PlainText = null;
}
}
return new RServiceResult<PoemGeoDateTag[]>(tags);
}
catch (Exception exp)
{
return new RServiceResult<PoemGeoDateTag[]>(null, exp.ToString());
}
}
/// <summary>
/// Database Context
/// </summary>