# retoor import logging from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse from devplacepy.database import ( get_table, build_pagination, get_news_images_by_uids, soft_delete, _now_iso, ) from devplacepy.utils import require_admin, time_ago from devplacepy.seo import base_seo_context, site_url, website_schema from devplacepy.responses import respond, action_result from devplacepy.schemas import AdminNewsOut from devplacepy.services.audit import record as audit logger = logging.getLogger(__name__) router = APIRouter() @router.get("/news", response_class=HTMLResponse) async def admin_news(request: Request, page: int = 1): admin = require_admin(request) news_table = get_table("news") total = news_table.count(deleted_at=None) pagination = build_pagination(page, total) offset = (pagination["page"] - 1) * pagination["per_page"] page_articles = list( news_table.find( deleted_at=None, order_by=["-synced_at"], _limit=pagination["per_page"], _offset=offset, ) ) images_by_news = get_news_images_by_uids([a["uid"] for a in page_articles]) enriched = [] for a in page_articles: enriched.append( { "article": a, "time_ago": time_ago(a["synced_at"]), "synced_at": a.get("synced_at", ""), "grade": a.get("grade", 0), "has_image": a["uid"] in images_by_news, } ) base = site_url(request) seo_ctx = base_seo_context( request, title="News - Admin", description="Manage DevPlace news articles.", robots="noindex,nofollow", breadcrumbs=[ {"name": "Home", "url": "/feed"}, {"name": "Admin", "url": "/admin"}, {"name": "News", "url": "/admin/news"}, ], schemas=[website_schema(base)], ) return respond( request, "admin_news.html", { **seo_ctx, "request": request, "user": admin, "articles": enriched, "pagination": pagination, "admin_section": "news", }, model=AdminNewsOut, ) @router.post("/news/{uid}/toggle") async def admin_news_toggle(request: Request, uid: str): admin = require_admin(request) news_table = get_table("news") article = news_table.find_one(uid=uid) if article: current = article.get("featured", 0) new_value = 0 if current else 1 news_table.update({"uid": uid, "featured": new_value}, ["uid"]) logger.info( f"Admin {admin['username']} toggled news {uid} featured={'off' if current else 'on'}" ) audit.record( request, "news.featured.toggle", user=admin, target_type="news", target_uid=uid, target_label=article.get("title"), old_value=current, new_value=new_value, summary=f"admin {admin['username']} toggled featured for news {article.get('title')}", links=[audit.target("news", uid, article.get("title"))], ) return action_result(request, "/admin/news") @router.post("/news/{uid}/publish") async def admin_news_publish(request: Request, uid: str): admin = require_admin(request) news_table = get_table("news") article = news_table.find_one(uid=uid) if article: current = article.get("status", "draft") new_status = "draft" if current == "published" else "published" news_table.update({"uid": uid, "status": new_status}, ["uid"]) logger.info(f"Admin {admin['username']} set news {uid} status={new_status}") audit.record( request, "news.publish.toggle", user=admin, target_type="news", target_uid=uid, target_label=article.get("title"), old_value=current, new_value=new_status, summary=f"admin {admin['username']} set news {article.get('title')} to {new_status}", links=[audit.target("news", uid, article.get("title"))], ) return action_result(request, "/admin/news") @router.post("/news/{uid}/landing") async def admin_news_landing(request: Request, uid: str): admin = require_admin(request) news_table = get_table("news") article = news_table.find_one(uid=uid) if article: current = article.get("show_on_landing", 0) new_value = 0 if current else 1 news_table.update({"uid": uid, "show_on_landing": new_value}, ["uid"]) logger.info( f"Admin {admin['username']} toggled news {uid} landing={'on' if not current else 'off'}" ) audit.record( request, "news.landing.toggle", user=admin, target_type="news", target_uid=uid, target_label=article.get("title"), old_value=current, new_value=new_value, summary=f"admin {admin['username']} toggled landing visibility for news {article.get('title')}", links=[audit.target("news", uid, article.get("title"))], ) return action_result(request, "/admin/news") @router.post("/news/{uid}/delete") async def admin_news_delete(request: Request, uid: str): admin = require_admin(request) news_table = get_table("news") article = news_table.find_one(uid=uid, deleted_at=None) if article: stamp = _now_iso() image_count = soft_delete("news_images", admin["uid"], stamp=stamp, news_uid=uid) soft_delete("news", admin["uid"], stamp=stamp, uid=uid) logger.info(f"Admin {admin['username']} soft-deleted news {uid}") audit.record( request, "news.delete", user=admin, target_type="news", target_uid=uid, target_label=article.get("title"), metadata={"image_count": image_count}, summary=f"admin {admin['username']} deleted news article {article.get('title')}", links=[audit.target("news", uid, article.get("title"))], ) return action_result(request, "/admin/news")