156 lines
4.3 KiB
Text
156 lines
4.3 KiB
Text
# Post Metadata Schema
|
|
#
|
|
# Canonical contract for any content item (blog, article, page, etc.).
|
|
# Consumed by:
|
|
# - ContentIndexer: validates *.ncl files, builds in-memory index
|
|
# - TypeDialog: generates web/CLI/TUI forms from this schema
|
|
# - DB migration: struct maps directly to posts table
|
|
#
|
|
# Usage — preferred (make_post factory):
|
|
#
|
|
# let schema = import "site/nickel/content/metadata/post_metadata.ncl" in
|
|
# schema.make_post {
|
|
# title = "My Rust Post",
|
|
# slug = "my-rust-post",
|
|
# date = "2024-01-15",
|
|
# category = "rust",
|
|
# }
|
|
#
|
|
# The factory merges site defaults (Layer 1) with the provided overrides
|
|
# (Layer 2) then applies PostMetadata as a contract for type checking.
|
|
|
|
let c = import "contracts.ncl" in
|
|
let d = import "defaults.ncl" in
|
|
let g = import "graph_contracts.ncl" in
|
|
|
|
{
|
|
PostMetadata = {
|
|
# --- Identity ---
|
|
|
|
id
|
|
| String
|
|
| c.NonEmptyString
|
|
| doc "Unique stable identifier. Inferred from filename stem when absent. Never changes after publication."
|
|
| optional,
|
|
|
|
title
|
|
| String
|
|
| c.NonEmptyString
|
|
| doc "Post title shown in listings, headings, and browser tab",
|
|
|
|
slug
|
|
| String
|
|
| c.SlugContract
|
|
| doc "URL path segment: lowercase, hyphens only. E.g. 'my-rust-post-2024'",
|
|
|
|
subtitle
|
|
| String
|
|
| doc "Secondary heading displayed under the title in the post view"
|
|
| optional,
|
|
|
|
excerpt
|
|
| String
|
|
| doc "Short summary (1-3 sentences) for index listings and SEO meta description"
|
|
| optional,
|
|
|
|
# --- Content file reference ---
|
|
|
|
content_file
|
|
| String
|
|
| doc "Filename of the content body relative to this *.ncl file. Inferred from slug when absent."
|
|
| optional,
|
|
|
|
# --- Publication ---
|
|
|
|
author
|
|
| String
|
|
| c.NonEmptyString
|
|
| doc "Author display name shown in post header and listings"
|
|
| default = "Jesús Pérez Lorenzo",
|
|
|
|
date
|
|
| String
|
|
| c.DateContract
|
|
| doc "Original publication date in YYYY-MM-DD format",
|
|
|
|
updated_at
|
|
| String
|
|
| c.DateContract
|
|
| doc "Date of last significant update in YYYY-MM-DD format"
|
|
| optional,
|
|
|
|
published
|
|
| Bool
|
|
| doc "False hides the post from all index listings and the API"
|
|
| default = true,
|
|
|
|
featured
|
|
| Bool
|
|
| doc "Pins to the top of the category listing and enables featured styling"
|
|
| default = false,
|
|
|
|
draft
|
|
| Bool
|
|
| doc "Draft posts are excluded from the index and not served by /api/content-render"
|
|
| default = false,
|
|
|
|
# --- Taxonomy ---
|
|
|
|
category
|
|
| String
|
|
| c.NonEmptyString
|
|
| doc "Primary category slug — must match the parent directory name (e.g. 'rust', 'devops')",
|
|
|
|
tags
|
|
| Array String
|
|
| doc "Secondary classification tags for filtering and related-content discovery"
|
|
| default = [],
|
|
|
|
# --- Display hints ---
|
|
|
|
read_time
|
|
| String
|
|
| c.ReadTimeContract
|
|
| doc "Estimated reading time for the listing card. E.g. '8 min read'. Auto-derived from word count when absent."
|
|
| optional,
|
|
|
|
sort_order
|
|
| Number
|
|
| doc "Manual position within the category listing. Lower values appear first. 0 = no preference."
|
|
| default = 0,
|
|
|
|
# --- SEO ---
|
|
|
|
image_url
|
|
| String
|
|
| doc "Open Graph and social share image. Absolute URL or root-relative path."
|
|
| optional,
|
|
|
|
thumbnail
|
|
| String
|
|
| doc "Card thumbnail image. Absolute URL or root-relative path. Falls back to image_url when absent."
|
|
| optional,
|
|
|
|
# --- Multilingual ---
|
|
|
|
translations
|
|
| { _ : c.TranslationEntry }
|
|
| doc "Map of language-code → { id, slug } for the translation. E.g. { es = { id = 'my-post', slug = 'mi-post-en-rust' } }."
|
|
| default = {},
|
|
|
|
# --- Knowledge graph ---
|
|
|
|
graph
|
|
| g.GraphMeta
|
|
| doc "Explicit relationships to other content items and on+re ontology nodes. Consumed by the content-graph feature. Ignored when the feature is not active."
|
|
| default = {},
|
|
},
|
|
|
|
# Factory function — Layer 1 (defaults) & Layer 2 (overrides) | contract.
|
|
# Not exported to JSON; accessible only via Nickel import.
|
|
make_post
|
|
| not_exported
|
|
| doc "Create a validated post metadata record. Provide at minimum: title, slug, date, category."
|
|
= fun overrides =>
|
|
d & overrides | PostMetadata,
|
|
}
|