145 lines
4.1 KiB
Python
Raw Normal View History

2026-05-11 22:12:43 +02:00
import logging
2026-05-14 04:12:19 +02:00
from datetime import datetime, timedelta, timezone
2026-05-11 22:12:43 +02:00
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
2026-06-09 18:48:08 +02:00
from devplacepy.database import (
get_table,
db,
load_comments,
resolve_by_slug,
get_news_images_by_uids,
get_user_bookmarks,
paginate,
)
2026-05-30 20:16:39 +02:00
from devplacepy.utils import get_current_user, time_ago, not_found
2026-06-05 20:05:07 +02:00
from devplacepy.content import canonical_redirect
2026-06-09 18:48:08 +02:00
from devplacepy.seo import (
base_seo_context,
website_schema,
site_url,
news_article_schema,
list_page_seo,
next_page_url,
)
2026-06-08 17:38:33 +02:00
from devplacepy.responses import respond
from devplacepy.schemas import NewsListOut, NewsDetailOut
2026-05-11 22:12:43 +02:00
logger = logging.getLogger(__name__)
router = APIRouter()
NEWS_MAX_AGE_DAYS = 4
@router.get("", response_class=HTMLResponse)
2026-06-05 05:36:18 +02:00
async def news_page(request: Request, before: str = None):
2026-05-11 22:12:43 +02:00
user = get_current_user(request)
2026-06-09 18:48:08 +02:00
cutoff = (
datetime.now(timezone.utc) - timedelta(days=NEWS_MAX_AGE_DAYS)
).isoformat()
2026-05-11 22:12:43 +02:00
news_table = get_table("news")
2026-06-05 05:36:18 +02:00
articles, next_cursor = paginate(
news_table,
before=before,
order=["-grade", "-synced_at"],
cursor_field="synced_at",
2026-05-11 22:12:43 +02:00
status="published",
synced_at={">=": cutoff},
2026-06-05 05:36:18 +02:00
)
2026-05-11 22:12:43 +02:00
article_uids = [a["uid"] for a in articles]
2026-05-23 06:20:27 +02:00
images_by_news = get_news_images_by_uids(article_uids)
2026-05-11 22:12:43 +02:00
enriched = []
for a in articles:
2026-06-09 18:48:08 +02:00
enriched.append(
{
"article": a,
"time_ago": time_ago(a["synced_at"]),
"image_url": images_by_news.get(a["uid"]),
"grade": a.get("grade", 0),
}
)
2026-05-11 22:12:43 +02:00
2026-05-30 20:16:39 +02:00
seo_ctx = list_page_seo(
2026-05-11 22:12:43 +02:00
request,
title="Developer News",
description="Curated developer news and industry signals. Stay ahead with hand-picked articles.",
2026-06-09 18:48:08 +02:00
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "News", "url": "/news"},
],
2026-06-05 20:05:07 +02:00
next_url=next_page_url(request, next_cursor),
2026-05-11 22:12:43 +02:00
)
2026-06-09 18:48:08 +02:00
return respond(
request,
"news.html",
{
**seo_ctx,
"request": request,
"user": user,
"articles": enriched,
"next_cursor": next_cursor,
},
model=NewsListOut,
)
2026-05-12 12:45:52 +02:00
@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")
2026-05-12 15:07:34 +02:00
article = resolve_by_slug(news_table, news_slug)
2026-05-12 12:45:52 +02:00
if not article:
2026-05-30 20:16:39 +02:00
raise not_found("News article not found")
2026-06-05 20:05:07 +02:00
redirect = canonical_redirect("news", article, news_slug)
if redirect:
return redirect
2026-05-12 12:45:52 +02:00
image_url = ""
if "news_images" in db.tables:
img = get_table("news_images").find_one(news_uid=article["uid"])
if img:
image_url = img["url"]
canonical_slug = article.get("slug", "") or article["uid"]
2026-05-30 20:16:39 +02:00
comments = load_comments("news", article["uid"], user)
2026-06-09 18:48:08 +02:00
bookmarked = bool(user) and article["uid"] in get_user_bookmarks(
user["uid"], "news", [article["uid"]]
)
2026-05-12 12:45:52 +02:00
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 "")[:200],
2026-05-23 03:21:55 +02:00
og_type="article",
og_image=image_url or None,
2026-06-09 18:48:08 +02:00
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "News", "url": "/news"},
{"name": article.get("title", "")[:60], "url": page_url},
],
2026-05-23 03:21:55 +02:00
schemas=[website_schema(base), news_article_schema(article, base, image_url)],
2026-05-12 12:45:52 +02:00
)
2026-06-09 18:48:08 +02:00
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,
},
model=NewsDetailOut,
)