using System.Text.Json.Serialization; namespace Elternbeirat.Contracts; /// /// A topic the FAQ entries are grouped under, as stored in PocketBase. /// /// /// A points at its topic through a relation, so the FAQ page /// groups the questions by topic and shows the groups in . /// Making the topic its own record (rather than a free-text field on each /// question) means the heading, its order and its optional /// are edited in one place and shared by every question in the group. /// public record FaqTopic { /// /// Gets the PocketBase record id. /// /// /// : 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. /// [JsonPropertyName("id")] public required string Id { get; init; } /// /// Gets the heading shown above the group of questions, e.g. /// "Mensa und Mittagessen". /// /// /// Free text; may contain umlauts and spaces. For the URL, see . /// : a topic without a heading has nothing to show, and /// the field is required in PocketBase. /// [JsonPropertyName("title")] public required string Title { get; init; } /// /// Gets the URL slug of the topic, e.g. "cafeteria". /// /// /// A URL path segment: lowercase letters, digits and single hyphens as /// separators, no umlauts. PocketBase enforces this on save via a field /// pattern (^[a-z0-9]+(-[a-z0-9]+)*$), so every stored slug is already /// canonical and the app can use it verbatim. /// /// /// : the slug is required in PocketBase and is the /// stable identifier of a topic, independent of its display title. /// [JsonPropertyName("slug")] public required string Slug { get; init; } /// /// Gets the optional introduction shown below the topic heading, as Markdown. /// /// /// The intro Markdown, or an empty string when the topic has none. /// [JsonPropertyName("intro")] public string Intro { get; init; } = string.Empty; /// /// Gets the sort order of the topic among the FAQ groups. /// /// /// The sort key; topics with smaller values appear first. /// [JsonPropertyName("order")] public double Order { get; init; } /// /// Gets the expanded relations returned by PocketBase, or /// when the query did not expand any. /// [JsonPropertyName("expand")] public FaqTopicExpand? Expand { get; init; } /// /// Gets the questions of this topic, ordered by , or an /// empty list when none are linked or the relation was not expanded. /// /// /// Convenience over : questions name their topic, so /// PocketBase returns them under the back-relation key when the query expands it /// (faqs_via_topic). The topic detail page reads them from here. They /// arrive unsorted, so this orders them by for a stable /// render. Drafts are dropped: PocketBase does not apply the topic query's /// public filter to the expanded children, so this keeps /// non-public questions () off the page. /// [JsonIgnore] public IReadOnlyList Faqs => Expand?.Faqs is { } faqs ? [.. faqs.Where(faq => faq.Public).OrderBy(faq => faq.Order)] : []; } /// /// The relations of a that PocketBase returns under /// expand when the query asks for them. /// /// /// The questions point at their topic, so PocketBase exposes them as the /// back-relation faqs_via_topic. reads through /// this. /// public record FaqTopicExpand { /// /// Gets the expanded questions of the faqs_via_topic back-relation, if it /// was expanded. /// [JsonPropertyName("faqs_via_topic")] public IReadOnlyList? Faqs { get; init; } }