//! Seed blog posts on each project (Phase 4). //! //! Drives the project blog page (`/p/{slug}/blog`) and per-project RSS //! (`/p/{slug}/blog/feed.xml`). Posts are published (`published_at = NOW()`); the //! owning project is already public and the creator non-sandbox, so they render to //! anonymous fans. `body_html` is pre-rendered the same way the live blog handler //! does (`render_creator_markdown`). use super::SeedError; use super::projects::SeededProject; use crate::db; use crate::formatting::slugify; use crate::markdown::render_creator_markdown; /// Publish every roster blog post under its project. pub async fn seed_blog(pool: &sqlx::PgPool, projects: &[SeededProject]) -> Result<(), SeedError> { for project in projects { for post in project.spec.blog { let slug = slugify(post.title); // Bodies are plain prose with no embedded media, so the CDN base used // for media-path rewriting is irrelevant here. let body_html = render_creator_markdown(post.body, project.user_id, ""); db::blog_posts::create_blog_post( pool, project.project.id, project.user_id, post.title, &slug, post.body, &body_html, true, // publish -> published_at = NOW() false, // web_only false, // show_on_landing (inert off the changelog project) ) .await?; tracing::info!(title = post.title, slug = %slug, "example seed: published blog post"); } } Ok(()) } /// Project the media-credits post is published under. Commonshare is the benefit /// account whose whole premise is the commons, so the attribution for the box's /// public-domain media belongs there rather than on a creator's storefront. const CREDITS_PROJECT_SLUG: &str = "commons-sampler"; /// Title (and, slugified, the URL) of the media-credits post. const CREDITS_TITLE: &str = "Media credits and sources"; /// Publish a credits post listing every curated asset on the box, with its /// licence and a link to the page that substantiates the claim. /// /// A licence field in `media-manifest.toml` is a note to ourselves; a visitor /// looking at a CC0 photograph on a public site needs somewhere to go. This is /// that somewhere. The public `/docs/credits` page is deliberately not it: that /// page enumerates what the *software* is built on and ships identically to /// production, where none of this media exists. /// /// No-ops when nothing is curated, so an uncurated box does not publish an empty /// credits page. pub async fn seed_media_credits( pool: &sqlx::PgPool, projects: &[SeededProject], assets: &super::manifest::ResolvedAssets, ) -> Result<(), SeedError> { let list = assets.attribution_markdown(); if list.is_empty() { tracing::info!("example seed: no curated media, skipping the credits post"); return Ok(()); } let Some(project) = projects .iter() .find(|p| p.spec.slug == CREDITS_PROJECT_SLUG) else { tracing::warn!( slug = CREDITS_PROJECT_SLUG, "example seed: credits project missing from the roster; credits not published" ); return Ok(()); }; let body = format!( "Every file on this demo is public domain or CC0. This is where each one \ came from.\n\n{list}\n\nNothing here is a real listing. The catalog is \ fabricated for demonstration; only the media is real, and only because \ its licence allows it." ); let slug = slugify(CREDITS_TITLE); let body_html = render_creator_markdown(&body, project.user_id, ""); db::blog_posts::create_blog_post( pool, project.project.id, project.user_id, CREDITS_TITLE, &slug, &body, &body_html, true, // publish false, // web_only false, // show_on_landing ) .await?; tracing::info!( assets = assets.len(), slug = %slug, "example seed: published media credits" ); Ok(()) }