2026-05-10 09:08:12 +02:00
|
|
|
import logging
|
|
|
|
|
from fastapi import APIRouter, Request
|
2026-05-11 03:14:43 +02:00
|
|
|
from fastapi.responses import HTMLResponse
|
2026-05-23 10:54:45 +02:00
|
|
|
from devplacepy.database import get_table, get_daily_topic, get_users_by_uids, get_comment_counts_by_post_uids, get_site_stats, get_top_authors
|
2026-05-12 15:07:34 +02:00
|
|
|
from devplacepy.attachments import get_attachments_batch
|
2026-05-23 08:34:13 +02:00
|
|
|
from devplacepy.content import enrich_items
|
2026-05-10 09:08:12 +02:00
|
|
|
from devplacepy.templating import templates
|
2026-05-23 08:34:13 +02:00
|
|
|
from devplacepy.utils import get_current_user
|
2026-05-30 20:16:39 +02:00
|
|
|
from devplacepy.seo import list_page_seo
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
2026-05-11 03:14:43 +02:00
|
|
|
PAGE_SIZE = 25
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
|
2026-05-11 03:14:43 +02:00
|
|
|
def get_feed_posts(user, tab: str = "all", topic: str = None, before: str = None):
|
|
|
|
|
posts_table = get_table("posts")
|
2026-05-29 00:45:07 +02:00
|
|
|
order = ["-stars", "-created_at"] if tab == "trending" else ["-created_at"]
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
if tab == "following":
|
2026-05-12 12:45:52 +02:00
|
|
|
if not user:
|
|
|
|
|
return [], None
|
2026-05-10 09:08:12 +02:00
|
|
|
follows = get_table("follows")
|
|
|
|
|
following = [f["following_uid"] for f in follows.find(follower_uid=user["uid"])]
|
2026-05-11 03:14:43 +02:00
|
|
|
if not following:
|
|
|
|
|
return [], None
|
2026-05-29 00:45:07 +02:00
|
|
|
clauses = [posts_table.table.columns.user_uid.in_(following)]
|
|
|
|
|
if before:
|
|
|
|
|
clauses.append(posts_table.table.columns.created_at < before)
|
|
|
|
|
posts = list(posts_table.find(*clauses, order_by=order, _limit=PAGE_SIZE + 1))
|
2026-05-11 03:14:43 +02:00
|
|
|
else:
|
2026-05-29 00:45:07 +02:00
|
|
|
filters = {}
|
|
|
|
|
if topic:
|
|
|
|
|
filters["topic"] = topic
|
2026-05-11 03:14:43 +02:00
|
|
|
if before:
|
2026-05-29 00:45:07 +02:00
|
|
|
filters["created_at"] = {"<": before}
|
|
|
|
|
posts = list(posts_table.find(**filters, order_by=order, _limit=PAGE_SIZE + 1))
|
2026-05-11 03:14:43 +02:00
|
|
|
|
|
|
|
|
has_more = len(posts) > PAGE_SIZE
|
|
|
|
|
posts = posts[:PAGE_SIZE]
|
|
|
|
|
|
2026-05-29 00:45:07 +02:00
|
|
|
next_cursor = posts[-1]["created_at"] if has_more and posts else None
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-05-11 03:14:43 +02:00
|
|
|
if not posts:
|
|
|
|
|
return [], next_cursor
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-05-23 08:34:13 +02:00
|
|
|
authors = get_users_by_uids([p["user_uid"] for p in posts])
|
|
|
|
|
counts = get_comment_counts_by_post_uids([p["uid"] for p in posts])
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-05-30 20:16:39 +02:00
|
|
|
result = enrich_items(posts, "post", authors, {"comment_count": counts}, user=user)
|
2026-05-11 03:14:43 +02:00
|
|
|
return result, next_cursor
|
2026-05-10 09:08:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
2026-05-11 03:14:43 +02:00
|
|
|
async def feed_page(request: Request, tab: str = "all", topic: str = None, before: str = None):
|
2026-05-12 12:45:52 +02:00
|
|
|
user = get_current_user(request)
|
2026-05-11 03:14:43 +02:00
|
|
|
posts, next_cursor = get_feed_posts(user, tab, topic, before)
|
2026-05-23 06:20:27 +02:00
|
|
|
stats = get_site_stats()
|
2026-05-23 10:54:45 +02:00
|
|
|
top_authors = get_top_authors(5)
|
2026-05-11 00:41:41 +02:00
|
|
|
daily_topic = get_daily_topic()
|
2026-05-10 09:08:12 +02:00
|
|
|
|
2026-05-12 15:07:34 +02:00
|
|
|
post_uids_list = [item["post"]["uid"] for item in posts]
|
|
|
|
|
attachments_map = get_attachments_batch("post", post_uids_list)
|
|
|
|
|
for item in posts:
|
|
|
|
|
item["attachments"] = attachments_map.get(item["post"]["uid"], [])
|
|
|
|
|
|
2026-05-30 20:16:39 +02:00
|
|
|
seo_ctx = list_page_seo(
|
2026-05-11 05:30:51 +02:00
|
|
|
request,
|
|
|
|
|
title="Feed",
|
|
|
|
|
description="Discover the latest developer discussions, projects, and community activity on DevPlace.",
|
|
|
|
|
breadcrumbs=[{"name": "Home", "url": "/feed"}, {"name": "Feed", "url": "/feed"}],
|
|
|
|
|
)
|
2026-05-13 23:03:06 +02:00
|
|
|
return templates.TemplateResponse(request, "feed.html", {
|
2026-05-11 05:30:51 +02:00
|
|
|
**seo_ctx,
|
2026-05-10 09:08:12 +02:00
|
|
|
"request": request,
|
|
|
|
|
"user": user,
|
|
|
|
|
"posts": posts,
|
|
|
|
|
"current_tab": tab,
|
|
|
|
|
"current_topic": topic,
|
2026-05-23 06:20:27 +02:00
|
|
|
"total_members": stats["total_members"],
|
|
|
|
|
"posts_today": stats["posts_today"],
|
|
|
|
|
"total_projects": stats["total_projects"],
|
|
|
|
|
"total_gists": stats["total_gists"],
|
2026-05-10 09:08:12 +02:00
|
|
|
"top_authors": top_authors,
|
2026-05-11 00:41:41 +02:00
|
|
|
"daily_topic": daily_topic,
|
2026-05-11 03:14:43 +02:00
|
|
|
"next_cursor": next_cursor,
|
2026-05-10 09:08:12 +02:00
|
|
|
})
|