feat(nadia): Add devlog route logic and schema update
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
This commit is contained in:
parent
6b3df26a52
commit
fda72c5afb
@ -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)
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -102,6 +102,22 @@
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<section class="project-devlog">
|
||||
<h3 class="project-section-label">Devlog</h3>
|
||||
{% 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 %}
|
||||
<div class="devlog-footer" style="padding-top: 1rem; text-align: center;">
|
||||
<a href="?before={{ devlog_next_cursor }}" class="btn btn-secondary btn-sm"><span class="icon">📄</span> Load More</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p class="empty-state">No devlog posts yet.</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if is_owner %}
|
||||
{% call modal('edit-project-modal', 'Edit Project') %}
|
||||
<form method="POST" action="/projects/edit/{{ project['slug'] or project['uid'] }}">
|
||||
@ -184,3 +200,5 @@ if (actions) {
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user