|
# retoor <retoor@molodetz.nl>
|
|
|
|
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")
|