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
238 lines
6.1 KiB
Python
238 lines
6.1 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
from devplacepy.schemas.base import _Out
|
|
from devplacepy.schemas.content import (
|
|
AttachmentOut,
|
|
CommentItemOut,
|
|
GistOut,
|
|
MessageOut,
|
|
NewsOut,
|
|
NotificationOut,
|
|
PollOut,
|
|
PostOut,
|
|
ProjectOut,
|
|
ReactionsOut,
|
|
UserOut,
|
|
)
|
|
|
|
|
|
class FeedItemOut(_Out):
|
|
post: PostOut
|
|
author: Optional[UserOut] = None
|
|
time_ago: Optional[str] = None
|
|
my_vote: int = 0
|
|
comment_count: int = 0
|
|
attachments: list[AttachmentOut] = []
|
|
recent_comments: list[CommentItemOut] = []
|
|
reactions: ReactionsOut = ReactionsOut()
|
|
bookmarked: bool = False
|
|
poll: Optional[PollOut] = None
|
|
|
|
|
|
class GistItemOut(_Out):
|
|
gist: GistOut
|
|
author: Optional[UserOut] = None
|
|
time_ago: Optional[str] = None
|
|
my_vote: int = 0
|
|
comment_count: int = 0
|
|
recent_comments: list[CommentItemOut] = []
|
|
|
|
|
|
class ProjectListItemOut(ProjectOut):
|
|
author_name: Optional[str] = None
|
|
my_vote: int = 0
|
|
recent_comments: list[CommentItemOut] = []
|
|
|
|
|
|
class NewsListItemOut(_Out):
|
|
article: NewsOut
|
|
time_ago: Optional[str] = None
|
|
image_url: Optional[str] = None
|
|
grade: Optional[int] = None
|
|
featured: Optional[int] = None
|
|
recent_comments: list[CommentItemOut] = []
|
|
|
|
|
|
class ConversationOut(_Out):
|
|
other_user: Optional[UserOut] = None
|
|
last_message: Optional[str] = None
|
|
last_message_at: Optional[str] = None
|
|
unread: bool = False
|
|
|
|
|
|
class MessageItemOut(_Out):
|
|
message: MessageOut
|
|
sender: Optional[UserOut] = None
|
|
is_mine: bool = False
|
|
time_ago: Optional[str] = None
|
|
attachments: list[AttachmentOut] = []
|
|
grouped: bool = False
|
|
|
|
|
|
class NotificationItemOut(_Out):
|
|
notification: NotificationOut
|
|
actor: Optional[UserOut] = None
|
|
time_ago: Optional[str] = None
|
|
|
|
|
|
class NotificationGroupOut(_Out):
|
|
label: Optional[str] = None
|
|
entries: list[NotificationItemOut] = []
|
|
|
|
|
|
class LeaderboardEntryOut(_Out):
|
|
uid: Optional[str] = None
|
|
username: Optional[str] = None
|
|
avatar_seed: Optional[str] = None
|
|
stars: Optional[int] = None
|
|
level: Optional[int] = None
|
|
rank: Optional[int] = None
|
|
|
|
|
|
class SavedItemOut(_Out):
|
|
target_type: Optional[str] = None
|
|
target_uid: Optional[str] = None
|
|
type_label: Optional[str] = None
|
|
title: Optional[str] = None
|
|
url: Optional[str] = None
|
|
time_ago: Optional[str] = None
|
|
|
|
|
|
class FeedOut(_Out):
|
|
posts: list[FeedItemOut] = []
|
|
current_tab: Optional[str] = None
|
|
current_topic: Optional[str] = None
|
|
search: Optional[str] = None
|
|
next_cursor: Optional[str] = None
|
|
total_members: Optional[int] = None
|
|
posts_today: Optional[int] = None
|
|
total_projects: Optional[int] = None
|
|
total_gists: Optional[int] = None
|
|
top_authors: list[UserOut] = []
|
|
daily_topic: Optional[Any] = None
|
|
online_users: list[UserOut] = []
|
|
|
|
|
|
class PostDetailOut(_Out):
|
|
post: PostOut
|
|
author: Optional[UserOut] = None
|
|
is_owner: bool = False
|
|
star_count: int = 0
|
|
my_vote: int = 0
|
|
time_ago: Optional[str] = None
|
|
comments: list[CommentItemOut] = []
|
|
attachments: list[AttachmentOut] = []
|
|
reactions: ReactionsOut = ReactionsOut()
|
|
bookmarked: bool = False
|
|
poll: Optional[PollOut] = None
|
|
comment_count: Optional[int] = None
|
|
related_posts: list[FeedItemOut] = []
|
|
topics: list[str] = []
|
|
|
|
|
|
class ProjectsOut(_Out):
|
|
projects: list[ProjectListItemOut] = []
|
|
current_tab: Optional[str] = None
|
|
search: Optional[str] = None
|
|
project_type: Optional[str] = None
|
|
total_count: Optional[int] = None
|
|
next_cursor: Optional[str] = None
|
|
total_members: Optional[int] = None
|
|
top_authors: list[UserOut] = []
|
|
|
|
|
|
class ProjectDetailOut(_Out):
|
|
project: ProjectOut
|
|
author: Optional[UserOut] = None
|
|
is_owner: bool = False
|
|
star_count: int = 0
|
|
my_vote: int = 0
|
|
time_ago: Optional[str] = None
|
|
comments: list[CommentItemOut] = []
|
|
attachments: list[AttachmentOut] = []
|
|
reactions: ReactionsOut = ReactionsOut()
|
|
bookmarked: bool = False
|
|
platforms: Optional[Any] = None
|
|
is_private: bool = False
|
|
read_only: bool = False
|
|
viewer_can_containers: bool = False
|
|
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):
|
|
gists: list[GistItemOut] = []
|
|
total_count: Optional[int] = None
|
|
next_cursor: Optional[str] = None
|
|
current_language: Optional[str] = None
|
|
search: Optional[str] = None
|
|
languages: list[tuple[str, str]] = []
|
|
gist_language_codes: list[str] = []
|
|
|
|
|
|
class GistDetailOut(_Out):
|
|
gist: GistOut
|
|
author: Optional[UserOut] = None
|
|
is_owner: bool = False
|
|
star_count: int = 0
|
|
my_vote: int = 0
|
|
time_ago: Optional[str] = None
|
|
comments: list[CommentItemOut] = []
|
|
attachments: list[AttachmentOut] = []
|
|
reactions: ReactionsOut = ReactionsOut()
|
|
bookmarked: bool = False
|
|
|
|
|
|
class NewsListOut(_Out):
|
|
articles: list[NewsListItemOut] = []
|
|
next_cursor: Optional[str] = None
|
|
|
|
|
|
class NewsDetailOut(_Out):
|
|
article: NewsOut
|
|
canonical_slug: Optional[str] = None
|
|
image_url: Optional[str] = None
|
|
grade: Optional[int] = None
|
|
time_ago: Optional[str] = None
|
|
comments: list[CommentItemOut] = []
|
|
bookmarked: bool = False
|
|
|
|
|
|
class MessagesOut(_Out):
|
|
conversations: list[ConversationOut] = []
|
|
messages: list[MessageItemOut] = []
|
|
other_user: Optional[UserOut] = None
|
|
current_conversation: Optional[str] = None
|
|
search: Optional[str] = None
|
|
other_online: bool = False
|
|
other_last_seen: Optional[str] = None
|
|
|
|
|
|
class NotificationsOut(_Out):
|
|
notification_groups: list[NotificationGroupOut] = []
|
|
next_cursor: Optional[str] = None
|
|
|
|
|
|
class LeaderboardOut(_Out):
|
|
entries: list[LeaderboardEntryOut] = []
|
|
user_rank: Optional[int] = None
|
|
total_members: Optional[int] = None
|
|
posts_today: Optional[int] = None
|
|
total_projects: Optional[int] = None
|
|
total_gists: Optional[int] = None
|
|
top_authors: list[UserOut] = []
|
|
featured_news: list[NewsOut] = []
|
|
|
|
|
|
class SavedOut(_Out):
|
|
items: list[SavedItemOut] = []
|
|
next_cursor: Optional[str] = None
|
|
|