# retoor import logging from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse from devplacepy.database import ( get_maturity, get_table, db, load_comments, resolve_by_slug, get_news_images_by_uids, get_recent_comments_by_target_uids, get_user_bookmarks, paginate, resolve_object_url, mark_notifications_read_by_target, ) from devplacepy.utils import get_current_user, time_ago, not_found from devplacepy.content import canonical_redirect from devplacepy.seo import ( base_seo_context, website_schema, site_url, news_article_schema, list_page_seo, next_page_url, ) from devplacepy.responses import respond from devplacepy.schemas import NewsListOut, NewsDetailOut logger = logging.getLogger(__name__) router = APIRouter() NEWS_MAX_AGE_DAYS = 4 @router.get("", response_class=HTMLResponse) async def news_page(request: Request, before: str = None): user = get_current_user(request) cutoff = ( datetime.now(timezone.utc) - timedelta(days=NEWS_MAX_AGE_DAYS) ).isoformat() news_table = get_table("news") articles, next_cursor = paginate( news_table, before=before, order=["-grade", "-synced_at"], cursor_field="synced_at", status="published", synced_at={">=": cutoff}, ) article_uids = [a["uid"] for a in articles] images_by_news = get_news_images_by_uids(article_uids) recent_comments = get_recent_comments_by_target_uids( "news", article_uids, 3, user ) enriched = [] for a in articles: enriched.append( { "article": a, "time_ago": time_ago(a["synced_at"]), "image_url": a.get("image_url", "") or images_by_news.get(a["uid"]), "grade": a.get("grade", 0), "featured": a.get("featured", 0), "recent_comments": recent_comments.get(a["uid"], []), } ) seo_ctx = list_page_seo( request, title="Developer News", description="Curated developer news and industry signals. Stay ahead with hand-picked articles.", breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "News", "url": "/news"}, ], next_url=next_page_url(request, next_cursor), ) return respond( request, "news.html", { **seo_ctx, "request": request, "user": user, "articles": enriched, "next_cursor": next_cursor, }, model=NewsListOut, ) @router.get("/{news_slug}", response_class=HTMLResponse) async def news_detail_page(request: Request, news_slug: str): user = get_current_user(request) news_table = get_table("news") article = resolve_by_slug(news_table, news_slug) if not article: raise not_found("News article not found") redirect = canonical_redirect("news", article, news_slug) if redirect: return redirect if user: mark_notifications_read_by_target( user["uid"], resolve_object_url("news", article["uid"]) ) image_url = article.get("image_url", "") or "" if not image_url and "news_images" in db.tables: img = get_table("news_images").find_one( news_uid=article["uid"], deleted_at=None, is_placeholder=0 ) if img: image_url = img["url"] canonical_slug = article.get("slug", "") or article["uid"] comments = load_comments("news", article["uid"], user) bookmarked = bool(user) and article["uid"] in get_user_bookmarks( user["uid"], "news", [article["uid"]] ) base = site_url(request) page_url = f"{base}/news/{canonical_slug}" seo_ctx = base_seo_context( request, title=article.get("title", "News Article"), description=article.get("description", "") or "", seo_target=("news", article["uid"]), og_type="article", og_image=image_url or None, breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "News", "url": "/news"}, {"name": article.get("title", "")[:60], "url": page_url}, ], schemas=[website_schema(base), news_article_schema(article, base, image_url)], ) return respond( request, "news_detail.html", { **seo_ctx, "request": request, "user": user, "article": article, "canonical_slug": canonical_slug, "image_url": image_url, "grade": article.get("grade", 0), "time_ago": time_ago(article["synced_at"]), "comments": comments, "bookmarked": bookmarked, "maturity": get_maturity("news", article["uid"])["level"], }, model=NewsDetailOut, )