diff --git a/GanjooRazor/Pages/People.cshtml b/GanjooRazor/Pages/People.cshtml new file mode 100644 index 00000000..75dda7ea --- /dev/null +++ b/GanjooRazor/Pages/People.cshtml @@ -0,0 +1,57 @@ +@page +@model GanjooRazor.Pages.PeopleModel +@{ + Layout = "_Layout"; +} +@section Head { + +} +@if (!string.IsNullOrEmpty(Model.LastError)) +{ + @Model.LastError +} +else +{ + + +
+ +
+ + + +
 
+ +
+

شجره‌نامه‌ها

+ @if (Model.FamilyTreeRoots == null || Model.FamilyTreeRoots.Count == 0) + { +

هنوز شجره‌نامه‌ای ثبت نشده است.

+ } + else + { + + } +
+ +
 
+ +
+ + + +
+ + @if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString())) + { + @Html.Raw(ViewData["TrackingScript"]) + } +} diff --git a/GanjooRazor/Pages/People.cshtml.cs b/GanjooRazor/Pages/People.cshtml.cs new file mode 100644 index 00000000..08a23c5d --- /dev/null +++ b/GanjooRazor/Pages/People.cshtml.cs @@ -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 +{ + /// + /// 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). + /// + public class PeopleModel : LoginPartialEnabledPageModel + { + public PeopleModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration) + { + } + + public string LastError { get; set; } + + public List FamilyTreeRoots { get; set; } + + public async Task 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>(await response.Content.ReadAsStringAsync()); + + return Page(); + } + } +} diff --git a/GanjooRazor/Pages/Person.cshtml b/GanjooRazor/Pages/Person.cshtml new file mode 100644 index 00000000..da883d21 --- /dev/null +++ b/GanjooRazor/Pages/Person.cshtml @@ -0,0 +1,135 @@ +@page "{id:int}" +@model GanjooRazor.Pages.PersonModel +@using DNTPersianUtils.Core +@{ + Layout = "_Layout"; +} +@section Head { + +} +@if (!string.IsNullOrEmpty(Model.LastError)) +{ + @Model.LastError +} +else +{ + + +
+ +
+ + + +
 
+ +
+

@Model.Person.Name

+ + @if (!string.IsNullOrEmpty(Model.Person.FamilyTreeCaption)) + { +

شجره‌نامه: @Model.Person.FamilyTreeCaption

+ } + + @if (Model.Person.BirthYearInLHijri != 0 || Model.Person.DeathYearInLHijri != 0) + { +

+ @if (Model.Person.BirthYearInLHijri != 0) + { + تولد: @Model.Person.BirthYearInLHijri.ToPersianNumbers() (قمری) @(Model.Person.ValidBirthDate ? "" : "- تخمینی") + } + @if (Model.Person.DeathYearInLHijri != 0) + { + — وفات: @Model.Person.DeathYearInLHijri.ToPersianNumbers() (قمری) @(Model.Person.ValidDeathDate ? "" : "- تخمینی") + } +

+ } + + @if (!string.IsNullOrEmpty(Model.Person.Description)) + { +

@Model.Person.Description

+ } + + @if (!string.IsNullOrEmpty(Model.Person.WikiUrl)) + { +

صفحهٔ ویکی‌پدیا

+ } +
+ + @if (Model.RelationRows.Count > 0) + { +
 
+
+

خویشاوندان

+
    + @foreach (var row in Model.RelationRows) + { +
  • + @row.Label — @row.OtherPersonName + @if (!string.IsNullOrEmpty(row.Note)) + { + (@row.Note) + } +
  • + } +
+
+ } + + @if (Model.AffiliationRows.Count > 0) + { +
 
+
+

وابستگی‌های غیرخویشاوندی

+
    + @foreach (var row in Model.AffiliationRows) + { +
  • + @row.SentenceBeforeOtherName@row.OtherPersonName@row.SentenceAfterOtherName + @if (!string.IsNullOrEmpty(row.Note)) + { + (@row.Note) + } +
  • + } +
+
+ } + + @if (Model.Poems != null && Model.Poems.Length > 0) + { +
 
+
+

اشعار برچسب‌خورده با این فرد

+
    + @foreach (var tag in Model.Poems) + { + if (tag.Poem != null) + { +
  • @tag.Poem.FullTitle
  • + } + } +
+
+ } + else + { +
 
+
+

هنوز شعری با این فرد برچسب‌گذاری نشده است.

+
+ } + +
 
+ +
+ + + +
+ + @if (ViewData["TrackingScript"] != null && !string.IsNullOrEmpty(ViewData["TrackingScript"].ToString())) + { + @Html.Raw(ViewData["TrackingScript"]) + } +} diff --git a/GanjooRazor/Pages/Person.cshtml.cs b/GanjooRazor/Pages/Person.cshtml.cs new file mode 100644 index 00000000..5e3af33c --- /dev/null +++ b/GanjooRazor/Pages/Person.cshtml.cs @@ -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 +{ + /// + /// 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. + /// + public class PersonModel : LoginPartialEnabledPageModel + { + public PersonModel(HttpClient httpClient, IConfiguration configuration) : base(httpClient, configuration) + { + } + + public string LastError { get; set; } + + public GanjoorRelatedPerson Person { get; set; } + + /// + /// one row per kinship edge, already resolved into display-ready text so the .cshtml + /// doesn't need to know the directional meaning of PersonRelationType + /// + public List RelationRows { get; set; } = new List(); + + /// + /// one row per non-family tie, same purpose as RelationRows + /// + public List AffiliationRows { get; set; } = new List(); + + public PoemGeoDateTag[] Poems { get; set; } + + public class PersonRelationDisplayRow + { + /// + /// the other person's role relative to the subject (e.g. "فرزند", "پدر/مادر", "نیا") + /// + public string Label { get; set; } + public int OtherPersonId { get; set; } + public string OtherPersonName { get; set; } + public string Note { get; set; } + } + + public class PersonAffiliationDisplayRow + { + /// + /// 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) + /// + 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 _affiliationRoleWords = + new System.Collections.Generic.Dictionary() + { + { 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 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(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(await relationsResponse.Content.ReadAsStringAsync()); + + foreach (var r in relationsData.Relations ?? new List()) + { + RelationRows.Add(new PersonRelationDisplayRow() + { + Label = RelationLabel(r), + OtherPersonId = r.OtherPersonId, + OtherPersonName = r.OtherPersonName, + Note = r.Note, + }); + } + + foreach (var a in relationsData.Affiliations ?? new List()) + { + 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(await poemsResponse.Content.ReadAsStringAsync()); + + return Page(); + } + } +} diff --git a/RMuseum/Controllers/GanjoorRelatedPersonController.cs b/RMuseum/Controllers/GanjoorRelatedPersonController.cs index 2e909cd5..603c75f4 100644 --- a/RMuseum/Controllers/GanjoorRelatedPersonController.cs +++ b/RMuseum/Controllers/GanjoorRelatedPersonController.cs @@ -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); } + /// + /// 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. + /// + /// + [HttpGet("familytrees")] + [AllowAnonymous] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorRelatedPerson[]))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task GetFamilyTreeRootsAsync() + { + var res = await _personService.GetFamilyTreeRootsAsync(); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + return Ok(res.Result); + } + /// /// get person by id /// /// /// - [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); } + /// + /// get a person along with all their kinship/affiliation edges, for the read-only + /// person/family-tree browsing page + /// + /// + /// + [HttpGet("{id:int}/relations")] + [AllowAnonymous] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(GanjoorPersonRelationsViewModel))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task GetPersonRelationsAsync(int id) + { + var res = await _personService.GetPersonRelationsAsync(id); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + return Ok(res.Result); + } + + /// + /// get the poems tagged with this person + /// + /// + /// + [HttpGet("{id:int}/poems")] + [AllowAnonymous] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(PoemGeoDateTag[]))] + [ProducesResponseType((int)HttpStatusCode.BadRequest, Type = typeof(string))] + public async Task GetPoemsByPersonAsync(int id) + { + var res = await _personService.GetPoemsByPersonAsync(id); + if (!string.IsNullOrEmpty(res.ExceptionString)) + return BadRequest(res.ExceptionString); + return Ok(res.Result); + } + /// /// person service /// diff --git a/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs b/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs new file mode 100644 index 00000000..a8d0a24d --- /dev/null +++ b/RMuseum/Models/Ganjoor/ViewModels/GanjoorPersonRelationsViewModel.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; + +namespace RMuseum.Models.Ganjoor.ViewModels +{ + /// + /// 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 + /// + public class GanjoorPersonRelationsViewModel + { + /// + /// the subject person + /// + public GanjoorRelatedPerson Person { get; set; } + + /// + /// kinship edges touching Person (either side) + /// + public List Relations { get; set; } + + /// + /// non-family ties touching Person (either side) + /// + public List Affiliations { get; set; } + } + + /// + /// one GanjoorPersonRelation row, resolved from Person's point of view + /// + public class GanjoorPersonRelationInfo + { + /// + /// the other person in this relation + /// + public int OtherPersonId { get; set; } + + /// + /// the other person's name (denormalized here so the client doesn't need a second lookup) + /// + public string OtherPersonName { get; set; } + + /// + /// the kind of kinship edge + /// + public PersonRelationType RelationType { get; set; } + + /// + /// see GanjoorPersonRelation.DegreeHint + /// + public int? DegreeHint { get; set; } + + /// + /// free-text note + /// + public string Note { get; set; } + + /// + /// true if the subject person is Person1 in the underlying GanjoorPersonRelation row - + /// needed by the client to render directional types (Parent, Ancestor) correctly + /// + public bool SubjectIsPerson1 { get; set; } + } + + /// + /// one GanjoorPersonAffiliation row, resolved from Person's point of view - same shape/purpose + /// as GanjoorPersonRelationInfo above, but for a non-family tie + /// + public class GanjoorPersonAffiliationInfo + { + /// + /// the other person in this tie + /// + public int OtherPersonId { get; set; } + + /// + /// the other person's name + /// + public string OtherPersonName { get; set; } + + /// + /// the kind of non-family tie + /// + public PersonAffiliationType AffiliationType { get; set; } + + /// + /// free-text note + /// + public string Note { get; set; } + + /// + /// true if the subject person is Person1 in the underlying GanjoorPersonAffiliation row + /// + public bool SubjectIsPerson1 { get; set; } + } +} diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml index 24710fa7..315681a6 100644 --- a/RMuseum/RMuseum.xml +++ b/RMuseum/RMuseum.xml @@ -2827,6 +2827,14 @@ + + + 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. + + + get person by id @@ -2834,6 +2842,21 @@ + + + get a person along with all their kinship/affiliation edges, for the read-only + person/family-tree browsing page + + + + + + + get the poems tagged with this person + + + + person service @@ -12843,6 +12866,95 @@ FullUrl + + + 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 + + + + + the subject person + + + + + kinship edges touching Person (either side) + + + + + non-family ties touching Person (either side) + + + + + one GanjoorPersonRelation row, resolved from Person's point of view + + + + + the other person in this relation + + + + + the other person's name (denormalized here so the client doesn't need a second lookup) + + + + + the kind of kinship edge + + + + + see GanjoorPersonRelation.DegreeHint + + + + + free-text note + + + + + true if the subject person is Person1 in the underlying GanjoorPersonRelation row - + needed by the client to render directional types (Parent, Ancestor) correctly + + + + + one GanjoorPersonAffiliation row, resolved from Person's point of view - same shape/purpose + as GanjoorPersonRelationInfo above, but for a non-family tie + + + + + the other person in this tie + + + + + the other person's name + + + + + the kind of non-family tie + + + + + free-text note + + + + + true if the subject person is Person1 in the underlying GanjoorPersonAffiliation row + + a more complete GanjoorPoem View Model @@ -17587,6 +17699,29 @@ + + + get people who caption a family tree (GanjoorRelatedPerson.FamilyTreeCaption not empty) - + used as the entry points for browsing family trees + + + + + + 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 + + + + + + + get the (approved, materialized) poem geo/date tags that name this person, each carrying + enough of its Poem to link to it + + + + Ganjoor Poems Content Privider Service @@ -21036,6 +21171,26 @@ + + + get people who caption a family tree + + + + + + get a person along with all their kinship/affiliation edges + + + + + + + get the (approved, materialized) poem geo/date tags that name this person + + + + Database Context diff --git a/RMuseum/Services/IGanjoorRelatedPersonService.cs b/RMuseum/Services/IGanjoorRelatedPersonService.cs index 7537526e..9696c30c 100644 --- a/RMuseum/Services/IGanjoorRelatedPersonService.cs +++ b/RMuseum/Services/IGanjoorRelatedPersonService.cs @@ -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 /// /// Task> GetPersonAsync(int id); + + /// + /// get people who caption a family tree (GanjoorRelatedPerson.FamilyTreeCaption not empty) - + /// used as the entry points for browsing family trees + /// + /// + Task> GetFamilyTreeRootsAsync(); + + /// + /// 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 + /// + /// + /// + Task> GetPersonRelationsAsync(int id); + + /// + /// get the (approved, materialized) poem geo/date tags that name this person, each carrying + /// enough of its Poem to link to it + /// + /// + /// + Task> GetPoemsByPersonAsync(int id); } } diff --git a/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs b/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs index b4d42e1f..0faaef8d 100644 --- a/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs +++ b/RMuseum/Services/Implementation/GanjoorRelatedPersonService.cs @@ -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 } } + /// + /// get people who caption a family tree + /// + /// + public async Task> GetFamilyTreeRootsAsync() + { + try + { + return new RServiceResult + ( + await _context.GanjoorRelatedPersons + .Where(p => !string.IsNullOrEmpty(p.FamilyTreeCaption)) + .OrderBy(p => p.FamilyTreeCaption) + .ToArrayAsync() + ); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + + /// + /// get a person along with all their kinship/affiliation edges + /// + /// + /// + public async Task> GetPersonRelationsAsync(int id) + { + try + { + var person = await _context.GanjoorRelatedPersons.Where(p => p.Id == id).SingleOrDefaultAsync(); + if (person == null) + { + return new RServiceResult(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(new GanjoorPersonRelationsViewModel() + { + Person = person, + Relations = relations, + Affiliations = affiliations, + }); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + + /// + /// get the (approved, materialized) poem geo/date tags that name this person + /// + /// + /// + public async Task> 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(tags); + } + catch (Exception exp) + { + return new RServiceResult(null, exp.ToString()); + } + } + /// /// Database Context ///