Files

110 lines
4.1 KiB
C#

using System.Text.Json.Serialization;
namespace Elternbeirat.Contracts;
/// <summary>
/// A single question and answer of the FAQ, as stored in PocketBase.
/// </summary>
/// <remarks>
/// On the FAQ page, entries are grouped by their <see cref="Topic"/>. The topic is
/// a PocketBase relation; <see cref="Topic"/> is the resolved topic record, present
/// only when the query expanded the relation (see <see cref="FaqExpand"/>).
/// </remarks>
public record Faq
{
/// <summary>
/// Gets the PocketBase record id.
/// </summary>
/// <remarks>
/// <see langword="required"/>: every stored record has an id. Modeling it as
/// required states which fields a record must carry, independent of the store,
/// so a future data source has to supply them too.
/// </remarks>
[JsonPropertyName("id")]
public required string Id { get; init; }
/// <summary>
/// Gets the question as a parent would phrase it.
/// </summary>
/// <remarks>
/// <see langword="required"/>: a FAQ entry without a question is incomplete, and
/// the field is required in PocketBase.
/// </remarks>
[JsonPropertyName("question")]
public required string Question { get; init; }
/// <summary>
/// Gets the answer as Markdown.
/// </summary>
/// <remarks>
/// <see langword="required"/>: a FAQ entry exists to answer its question, so an
/// entry without an answer is incomplete. The field is required in PocketBase.
/// </remarks>
[JsonPropertyName("answer")]
public required string Answer { get; init; }
/// <summary>
/// Gets the sort order of the question within its topic.
/// </summary>
/// <value>
/// The sort key; questions with smaller values appear first.
/// </value>
[JsonPropertyName("order")]
public double Order { get; init; }
/// <summary>
/// Gets whether this entry is published and may be shown to visitors.
/// </summary>
/// <value>
/// <see langword="true"/> when the entry is public, <see langword="false"/> for a
/// draft that must stay hidden.
/// </value>
/// <remarks>
/// Collection queries filter on <c>public=true</c> server-side, so entries loaded
/// that way are always public and never need this checked. It matters only for the
/// <c>faqs_via_topic</c> back-relation expand behind <see cref="FaqTopic.Faqs"/>:
/// PocketBase does not apply the topic query's <c>public</c> filter to expanded
/// child records, so a draft question would otherwise leak onto the topic page.
/// That getter filters on this to keep drafts hidden.
/// </remarks>
[JsonPropertyName("public")]
public bool Public { get; init; }
/// <summary>
/// Gets the expanded relations returned by PocketBase, or <see langword="null"/>
/// when the query did not expand any.
/// </summary>
[JsonPropertyName("expand")]
public FaqExpand? Expand { get; init; }
/// <summary>
/// Gets the resolved topic this entry is grouped under, or <see langword="null"/>
/// when the query did not expand the topic relation.
/// </summary>
/// <remarks>
/// Convenience over <see cref="Expand"/>: the FAQ page reads the grouping
/// heading from here. It is <see langword="null"/> unless the entry was loaded
/// with the topic relation expanded.
/// </remarks>
[JsonIgnore]
public FaqTopic? Topic => Expand?.Topic;
}
/// <summary>
/// The relations of a <see cref="Faq"/> that PocketBase returns under
/// <c>expand</c> when the query asks for them.
/// </summary>
/// <remarks>
/// One property per expandable relation, named after its PocketBase field, so the
/// JSON <c>expand</c> object maps straight onto it. <see cref="Faq.Topic"/> reads
/// through this.
/// </remarks>
public record FaqExpand
{
/// <summary>
/// Gets the expanded topic of the <c>topic</c> relation, if it was expanded.
/// </summary>
[JsonPropertyName("topic")]
public FaqTopic? Topic { get; init; }
}