Render blog posts from Forgejo
This commit is contained in:
parent
6671a01d26
commit
0a9b052beb
6 changed files with 366 additions and 48 deletions
|
|
@ -83,6 +83,11 @@ async def build_live_prototype_payload(
|
|||
content_repos = [summary for summary in repo_summaries if summary is not None]
|
||||
course_repos = [summary for summary in content_repos if summary["lesson_count"] > 0]
|
||||
post_repos = [summary for summary in content_repos if summary["blog_count"] > 0]
|
||||
blog_posts = sorted(
|
||||
[post for summary in post_repos for post in summary["blog_posts"]],
|
||||
key=lambda post: str(post.get("updated_at", "")),
|
||||
reverse=True,
|
||||
)
|
||||
public_issues = await _recent_public_issues(
|
||||
client,
|
||||
public_repos,
|
||||
|
|
@ -128,7 +133,7 @@ async def build_live_prototype_payload(
|
|||
),
|
||||
"source_of_truth": source_cards,
|
||||
"featured_courses": [_course_card(summary) for summary in course_repos[:6]],
|
||||
"recent_posts": [_post_card(summary) for summary in post_repos[:6]],
|
||||
"recent_posts": [_post_card(post) for post in blog_posts[:6]],
|
||||
"upcoming_events": _event_cards(calendar_feeds, settings.calendar_event_limit),
|
||||
"recent_discussions": await asyncio.gather(
|
||||
*[_discussion_card(client, issue) for issue in public_issues],
|
||||
|
|
@ -232,9 +237,25 @@ async def _summarize_repo(
|
|||
)
|
||||
|
||||
blog_count = 0
|
||||
blog_posts: list[dict[str, object]] = []
|
||||
if has_blogs:
|
||||
blog_entries = await client.list_directory(owner_login, repo_name, "blogs")
|
||||
blog_count = sum(1 for entry in blog_entries if entry.get("type") == "dir")
|
||||
blog_dirs = _sorted_dir_entries(blog_entries)
|
||||
blog_count = len(blog_dirs)
|
||||
blog_posts = await asyncio.gather(
|
||||
*[
|
||||
_summarize_blog_post(
|
||||
client,
|
||||
owner_login,
|
||||
repo_name,
|
||||
str(repo.get("full_name", f"{owner_login}/{repo_name}")),
|
||||
str(repo.get("description") or ""),
|
||||
str(repo.get("updated_at", "")),
|
||||
str(blog_dir.get("name", "")),
|
||||
)
|
||||
for blog_dir in blog_dirs
|
||||
],
|
||||
)
|
||||
|
||||
return {
|
||||
"name": repo_name,
|
||||
|
|
@ -245,6 +266,7 @@ async def _summarize_repo(
|
|||
"lesson_count": lesson_count,
|
||||
"chapter_count": chapter_count,
|
||||
"blog_count": blog_count,
|
||||
"blog_posts": blog_posts,
|
||||
"updated_at": repo.get("updated_at", ""),
|
||||
"course_outline": course_outline,
|
||||
}
|
||||
|
|
@ -266,15 +288,20 @@ def _course_card(summary: dict[str, Any]) -> dict[str, object]:
|
|||
}
|
||||
|
||||
|
||||
def _post_card(summary: dict[str, Any]) -> dict[str, object]:
|
||||
post_count = int(summary["blog_count"])
|
||||
label = "1 post folder detected" if post_count == 1 else f"{post_count} post folders detected"
|
||||
def _post_card(post: dict[str, Any]) -> dict[str, object]:
|
||||
return {
|
||||
"title": summary["name"],
|
||||
"repo": summary["full_name"],
|
||||
"kind": "Repo with /blogs/",
|
||||
"summary": f"{label}. {summary['description']}",
|
||||
"updated_at": summary["updated_at"],
|
||||
"title": post["title"],
|
||||
"owner": post["owner"],
|
||||
"name": post["name"],
|
||||
"repo": post["repo"],
|
||||
"slug": post["slug"],
|
||||
"kind": "Blog post",
|
||||
"summary": post["summary"],
|
||||
"path": post["path"],
|
||||
"file_path": post["file_path"],
|
||||
"html_url": post["html_url"],
|
||||
"body": post["body"],
|
||||
"updated_at": post["updated_at"],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -474,6 +501,80 @@ def _auth_payload(
|
|||
}
|
||||
|
||||
|
||||
async def _summarize_blog_post(
|
||||
client: ForgejoClient,
|
||||
owner: str,
|
||||
repo: str,
|
||||
full_name: str,
|
||||
repo_description: str,
|
||||
updated_at: str,
|
||||
post_name: str,
|
||||
) -> dict[str, object]:
|
||||
post_path = f"blogs/{post_name}"
|
||||
fallback_title = _display_name(post_name)
|
||||
|
||||
try:
|
||||
post_entries = await client.list_directory(owner, repo, post_path)
|
||||
except ForgejoClientError:
|
||||
return _empty_blog_post(
|
||||
owner,
|
||||
repo,
|
||||
full_name,
|
||||
post_name,
|
||||
fallback_title,
|
||||
repo_description,
|
||||
updated_at,
|
||||
post_path,
|
||||
)
|
||||
|
||||
markdown_files = _markdown_file_entries(post_entries)
|
||||
if not markdown_files:
|
||||
return _empty_blog_post(
|
||||
owner,
|
||||
repo,
|
||||
full_name,
|
||||
post_name,
|
||||
fallback_title,
|
||||
repo_description,
|
||||
updated_at,
|
||||
post_path,
|
||||
)
|
||||
|
||||
markdown_name = str(markdown_files[0]["name"])
|
||||
markdown_path = f"{post_path}/{markdown_name}"
|
||||
|
||||
try:
|
||||
file_payload = await client.get_file_content(owner, repo, markdown_path)
|
||||
except ForgejoClientError:
|
||||
return _empty_blog_post(
|
||||
owner,
|
||||
repo,
|
||||
full_name,
|
||||
post_name,
|
||||
fallback_title,
|
||||
repo_description,
|
||||
updated_at,
|
||||
post_path,
|
||||
file_path=markdown_path,
|
||||
html_url=str(markdown_files[0].get("html_url", "")),
|
||||
)
|
||||
|
||||
metadata, body = _parse_frontmatter(str(file_payload.get("content", "")))
|
||||
return {
|
||||
"slug": post_name,
|
||||
"title": str(metadata.get("title") or _display_name(markdown_name) or fallback_title),
|
||||
"owner": owner,
|
||||
"name": repo,
|
||||
"repo": full_name,
|
||||
"summary": str(metadata.get("summary") or repo_description or ""),
|
||||
"path": post_path,
|
||||
"file_path": str(file_payload.get("path", markdown_path)),
|
||||
"html_url": str(file_payload.get("html_url", "")),
|
||||
"body": body,
|
||||
"updated_at": updated_at,
|
||||
}
|
||||
|
||||
|
||||
async def _summarize_lesson(
|
||||
client: ForgejoClient,
|
||||
owner: str,
|
||||
|
|
@ -489,16 +590,7 @@ async def _summarize_lesson(
|
|||
except ForgejoClientError:
|
||||
return _empty_lesson(lesson_name, fallback_title, lesson_path)
|
||||
|
||||
markdown_files = sorted(
|
||||
[
|
||||
entry
|
||||
for entry in lesson_entries
|
||||
if entry.get("type") == "file"
|
||||
and isinstance(entry.get("name"), str)
|
||||
and str(entry.get("name", "")).lower().endswith(".md")
|
||||
],
|
||||
key=lambda entry: str(entry["name"]),
|
||||
)
|
||||
markdown_files = _markdown_file_entries(lesson_entries)
|
||||
if not markdown_files:
|
||||
return _empty_lesson(lesson_name, fallback_title, lesson_path)
|
||||
|
||||
|
|
@ -539,6 +631,19 @@ def _sorted_dir_entries(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|||
)
|
||||
|
||||
|
||||
def _markdown_file_entries(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
return sorted(
|
||||
[
|
||||
entry
|
||||
for entry in entries
|
||||
if entry.get("type") == "file"
|
||||
and isinstance(entry.get("name"), str)
|
||||
and str(entry.get("name", "")).lower().endswith(".md")
|
||||
],
|
||||
key=lambda entry: str(entry["name"]),
|
||||
)
|
||||
|
||||
|
||||
def _display_name(value: str) -> str:
|
||||
cleaned = value.strip().rsplit(".", 1)[0]
|
||||
cleaned = cleaned.replace("_", " ").replace("-", " ")
|
||||
|
|
@ -598,6 +703,34 @@ def _empty_lesson(
|
|||
}
|
||||
|
||||
|
||||
def _empty_blog_post(
|
||||
owner: str,
|
||||
repo: str,
|
||||
full_name: str,
|
||||
post_name: str,
|
||||
title: str,
|
||||
summary: str,
|
||||
updated_at: str,
|
||||
post_path: str,
|
||||
*,
|
||||
file_path: str = "",
|
||||
html_url: str = "",
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
"slug": post_name,
|
||||
"title": title,
|
||||
"owner": owner,
|
||||
"name": repo,
|
||||
"repo": full_name,
|
||||
"summary": summary,
|
||||
"path": post_path,
|
||||
"file_path": file_path,
|
||||
"html_url": html_url,
|
||||
"body": "",
|
||||
"updated_at": updated_at,
|
||||
}
|
||||
|
||||
|
||||
def _parse_frontmatter(markdown: str) -> tuple[dict[str, str], str]:
|
||||
if not markdown.startswith("---\n"):
|
||||
return {}, markdown.strip()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue