feat(blog): add markdown blog sourced from a gitea repo
posts are markdown files with yaml frontmatter (title, slug, date;
optional draft/public) in the grenade/blog repo. the worker's new
BlogSource polls the repo — one branch-tip request when nothing
changed — and upserts posts into events with source='blog' and
occurred_at from the frontmatter date, so imported posts keep their
original publish dates and backfill the contribution graph.
- new /v1/blog and /v1/blog/{slug} endpoints over the existing
EventReader port; drafts stay hidden via the public gate
- new /blog and /blog/:slug routes, nav link, activity-feed entry
with post icon and filter toggle; relative image srcs resolve to
gitea raw urls
- shared Markdown component extracted from ProjectPage
- vite proxy target overridable via API_PROXY_TARGET for local dev
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ use clap::Parser;
|
||||
use moments_core::{EventSource, run_poller};
|
||||
use moments_data::{
|
||||
PgStore,
|
||||
blog::{BlogConfig, BlogSource},
|
||||
bugzilla::{BugzillaConfig, BugzillaSource},
|
||||
gitea::{GiteaConfig, GiteaSource},
|
||||
github::{GithubConfig, GithubSource},
|
||||
@@ -102,6 +103,19 @@ struct Args {
|
||||
/// Seconds between bugzilla creator-query polls (defaults to 24h).
|
||||
#[arg(long, env = "BUGZILLA_POLL_INTERVAL_SECS", default_value = "86400")]
|
||||
bugzilla_interval_secs: u64,
|
||||
|
||||
/// Gitea repo holding blog posts (markdown + frontmatter at the repo
|
||||
/// root, on `GITEA_HOST`). Empty string disables blog ingestion.
|
||||
#[arg(long, env = "BLOG_REPO", default_value = "grenade/blog")]
|
||||
blog_repo: String,
|
||||
|
||||
#[arg(long, env = "BLOG_BRANCH", default_value = "main")]
|
||||
blog_branch: String,
|
||||
|
||||
/// Seconds between blog repo polls (cheap: one branch-tip request when
|
||||
/// nothing changed).
|
||||
#[arg(long, env = "BLOG_POLL_INTERVAL_SECS", default_value = "600")]
|
||||
blog_interval_secs: u64,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -185,6 +199,20 @@ async fn main() -> anyhow::Result<()> {
|
||||
},
|
||||
)) as Arc<dyn EventSource>;
|
||||
|
||||
let blog = (!args.blog_repo.is_empty()).then(|| {
|
||||
Arc::new(BlogSource::new(
|
||||
http.clone(),
|
||||
store.clone(),
|
||||
store.clone(),
|
||||
BlogConfig {
|
||||
host: args.gitea_host.clone(),
|
||||
repo: args.blog_repo.clone(),
|
||||
branch: args.blog_branch.clone(),
|
||||
token: args.gitea_token.clone(),
|
||||
},
|
||||
)) as Arc<dyn EventSource>
|
||||
});
|
||||
|
||||
info!(
|
||||
github_user = args.github_user,
|
||||
gitea_host = args.gitea_host,
|
||||
@@ -201,6 +229,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
gitea_interval_secs = args.gitea_interval_secs,
|
||||
hg_interval_secs = args.hg_interval_secs,
|
||||
bugzilla_interval_secs = args.bugzilla_interval_secs,
|
||||
blog_repo = args.blog_repo,
|
||||
blog_branch = args.blog_branch,
|
||||
blog_interval_secs = args.blog_interval_secs,
|
||||
"worker started"
|
||||
);
|
||||
|
||||
@@ -210,6 +241,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
let gitea_interval = Duration::from_secs(args.gitea_interval_secs);
|
||||
let hg_interval = Duration::from_secs(args.hg_interval_secs);
|
||||
let bugzilla_interval = Duration::from_secs(args.bugzilla_interval_secs);
|
||||
let blog_interval = Duration::from_secs(args.blog_interval_secs);
|
||||
|
||||
let github_task = tokio::spawn(async move { run_poller(github, interval).await });
|
||||
let github_search_task =
|
||||
@@ -220,6 +252,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
let hg_task = tokio::spawn(async move { run_poller(hg, hg_interval).await });
|
||||
let bugzilla_task =
|
||||
tokio::spawn(async move { run_poller(bugzilla, bugzilla_interval).await });
|
||||
let blog_task =
|
||||
blog.map(|src| tokio::spawn(async move { run_poller(src, blog_interval).await }));
|
||||
|
||||
tokio::signal::ctrl_c().await?;
|
||||
info!("shutdown signal received");
|
||||
@@ -229,6 +263,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
gitea_task.abort();
|
||||
hg_task.abort();
|
||||
bugzilla_task.abort();
|
||||
if let Some(task) = blog_task {
|
||||
task.abort();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user