From fda72c5afb99db6df9e5e4967b5d78d58f58c485 Mon Sep 17 00:00:00 2001 From: typosaurus Date: Sun, 26 Jul 2026 21:10:15 +0000 Subject: [PATCH] feat(nadia): Add devlog route logic and schema update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Outcome: done Changed: devplacepy/routers/projects/index.py:9-10,17-20,178-241, devplacepy/schemas/listings.py:166-167, devplacepy/templates/project_detail.html:105-119 Verified by: `make test` not runnable — Python 3.11 environment cannot install `dataset` (requires >=3.12). Pre-existing limitation, not change-specific. Static checks (`py_compile`, `pyflakes`) pass clean on all changed files. Findings: - devplacepy/routers/projects/index.py:178 — `project_detail()` accepts optional `before: str = None` query param. - devplacepy/routers/projects/index.py:225-241 — Devlog posts fetched via `get_project_devlog()`, enriched with attachments/reactions/bookmarks/polls matching feed.py. - devplacepy/schemas/listings.py:166-167 — `ProjectDetailOut` gains `devlog_posts: list[FeedItemOut]` and `devlog_next_cursor: Optional[str]`. - devplacepy/templates/project_detail.html:105-119 — Devlog section reuses `_post_card.html`, shows Load More pagination when cursor present, empty-state message when none. Open: none Confidence: high - criteria addressed, static analysis clean, pattern matches existing code. Typosaurus-Run: 45c0aee6df2649ffaa248729bdfd5841 Typosaurus-Node: 8c955653b126445bac9cc5496369bd7e Typosaurus-Agent: @nadia Refs: #135 --- devplacepy/routers/projects/index.py | 30 +++++++++++++++++++++++- devplacepy/schemas/listings.py | 3 +++ devplacepy/templates/project_detail.html | 18 ++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/devplacepy/routers/projects/index.py b/devplacepy/routers/projects/index.py index aac1fe21..ad434b25 100644 --- a/devplacepy/routers/projects/index.py +++ b/devplacepy/routers/projects/index.py @@ -6,6 +6,7 @@ from sqlalchemy import or_ from fastapi import Depends, APIRouter, Request from devplacepy.models import ProjectForm, ProjectEditForm, ProjectFlagForm, ForkForm from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse +from devplacepy.attachments import get_attachments_batch from devplacepy.database import ( get_table, get_users_by_uids, @@ -13,6 +14,10 @@ from devplacepy.database import ( get_site_stats, get_user_votes, get_recent_comments_by_target_uids, + get_reactions_by_targets, + get_user_bookmarks, + get_polls_by_post_uids, + get_project_devlog, paginate, text_search_clause, resolve_by_slug, @@ -171,7 +176,7 @@ async def projects_page( ) @router.get("/{project_slug}", response_class=HTMLResponse) -async def project_detail(request: Request, project_slug: str): +async def project_detail(request: Request, project_slug: str, before: str = None): user = get_current_user(request) detail = load_detail("projects", "project", project_slug, user) if not detail: @@ -216,6 +221,25 @@ async def project_detail(request: Request, project_slug: str): if parent else None ) + + devlog_posts, devlog_next_cursor = get_project_devlog( + project["uid"], before=before, viewer=user + ) + if devlog_posts: + post_uids = [item["post"]["uid"] for item in devlog_posts] + attachments_map = get_attachments_batch("post", post_uids) + reactions_map = get_reactions_by_targets("post", post_uids, user) + bookmark_set = ( + get_user_bookmarks(user["uid"], "post", post_uids) if user else set() + ) + polls_map = get_polls_by_post_uids(post_uids, user) + for item in devlog_posts: + uid = item["post"]["uid"] + item["attachments"] = attachments_map.get(uid, []) + item["reactions"] = reactions_map.get(uid, {"counts": {}, "mine": []}) + item["bookmarked"] = uid in bookmark_set + item["poll"] = polls_map.get(uid) + return respond( request, "project_detail.html", @@ -235,6 +259,8 @@ async def project_detail(request: Request, project_slug: str): "forked_from": forked_from, "fork_count": count_forks(project["uid"]), "file_count": count_files(project["uid"]), + "devlog_posts": devlog_posts, + "devlog_next_cursor": devlog_next_cursor, }, ), model=ProjectDetailOut, @@ -464,3 +490,5 @@ async def set_project_readonly( request: Request, project_slug: str, data: Annotated[ProjectFlagForm, Depends(json_or_form(ProjectFlagForm))] ): return _set_project_flag(request, project_slug, "read_only", data.value) + + diff --git a/devplacepy/schemas/listings.py b/devplacepy/schemas/listings.py index b3b7f2a7..ecbab0b7 100644 --- a/devplacepy/schemas/listings.py +++ b/devplacepy/schemas/listings.py @@ -163,6 +163,8 @@ class ProjectDetailOut(_Out): forked_from: Optional[dict] = None fork_count: int = 0 file_count: int = 0 + devlog_posts: list[FeedItemOut] = [] + devlog_next_cursor: Optional[str] = None class GistsOut(_Out): @@ -232,3 +234,4 @@ class LeaderboardOut(_Out): class SavedOut(_Out): items: list[SavedItemOut] = [] next_cursor: Optional[str] = None + diff --git a/devplacepy/templates/project_detail.html b/devplacepy/templates/project_detail.html index 2b57ad19..df14fe10 100644 --- a/devplacepy/templates/project_detail.html +++ b/devplacepy/templates/project_detail.html @@ -102,6 +102,22 @@ +
+ + {% if devlog_posts %} + {% for item in devlog_posts %} + {% set _author = item.author %}{% set _time = item.time_ago %}{% set _show_share = false %}{% set _show_comment_form = false %}{% include "_post_card.html" %} + {% endfor %} + {% if devlog_next_cursor %} + + {% endif %} + {% else %} +

No devlog posts yet.

+ {% endif %} +
+ {% if is_owner %} {% call modal('edit-project-modal', 'Edit Project') %}
@@ -184,3 +200,5 @@ if (actions) { } {% endblock %} + +