diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6caebda..0cbf8c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,10 @@
- Nach jeder Aktion zurück zur Liste, damit sich eine Warteschlange am Stück abarbeiten lässt
- Verlinkt aus der Navigation aller Admin-Seiten, mit Zähler im Dashboard
+### 📨 Telegram
+ - Freigabe-Meldung enthält jetzt zwei Links: „Diesen Artikel prüfen" (Detailseite) und „Alle wartenden Artikel" (Warteschlange)
+ - Pipeline-Zusammenfassung verlinkt die Warteschlange, sobald etwas darauf wartet
+
### 🧹 Status-Altlasten entfernt
- Artikelliste zeigte interne Kürzel (`pending_review`, `approved`, `error`) — jetzt überall Klartext
- Status `review` (Relevanz-Warnzone 60–79) wurde als „Rewrite" angezeigt, was etwas anderes bedeutet — heißt jetzt „Niedrige Relevanz"
diff --git a/backend/app/telegram_bot.py b/backend/app/telegram_bot.py
index d0972c6..bc0ca7f 100644
--- a/backend/app/telegram_bot.py
+++ b/backend/app/telegram_bot.py
@@ -220,9 +220,17 @@ def notify_new_draft(
send_message(text, reply_markup=keyboard)
-def _portal_article_url(article_id: Any) -> str:
+def _portal_url(path: str) -> str:
base = (get_settings().portal_base_url or "").rstrip("/")
- return f"{base}/admin/articles/{article_id}"
+ return f"{base}{path}"
+
+
+def _portal_article_url(article_id: Any) -> str:
+ return _portal_url(f"/admin/articles/{article_id}")
+
+
+def _portal_queue_url() -> str:
+ return _portal_url("/admin/freigabe")
def _article_image_url(article: dict[str, Any]) -> str | None:
@@ -257,7 +265,8 @@ def notify_pending_review(article: dict[str, Any], score: int) -> None:
]
if tags_str:
text_parts.append(f"🏷 {tags_str}")
- text_parts.append(f'🔗 Im Portal prüfen und freigeben')
+ text_parts.append(f'🔗 Diesen Artikel prüfen')
+ text_parts.append(f'📋 Alle wartenden Artikel')
text_parts.append(
"Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant."
)
@@ -373,6 +382,7 @@ def notify_pipeline_done(stats: dict[str, Any]) -> None:
lines.append(f"📝 Drafts erstellt: {drafts}")
if pending_review:
lines.append(f"🕐 Wartet auf Freigabe: {pending_review}")
+ lines.append(f'📋 Freigabe-Warteschlange öffnen')
if rejected:
lines.append(f"🚫 Abgelehnt (Score): {rejected}")
if quality_gate_rejected:
diff --git a/backend/tests/test_editorial_review.py b/backend/tests/test_editorial_review.py
index 068183f..52eb98b 100644
--- a/backend/tests/test_editorial_review.py
+++ b/backend/tests/test_editorial_review.py
@@ -425,6 +425,48 @@ class TestReviewQueuePage(EditorialReviewTestBase):
self.assertIn("/admin/login", response.headers["location"])
+class TestTelegramLinks(EditorialReviewTestBase):
+ def test_pending_notification_links_to_article_and_to_the_queue(self) -> None:
+ from backend.app import telegram_bot
+
+ os.environ["PORTAL_BASE_URL"] = "https://news.example.de"
+ config_module.get_settings.cache_clear()
+ try:
+ article_id = self._create_article(status="pending_review", rewritten=LONG_TEXT)
+ article = get_article_by_id(article_id)
+
+ with patch.object(telegram_bot, "send_photo_message") as photo, \
+ patch.object(telegram_bot, "send_message") as plain:
+ telegram_bot.notify_pending_review(article, score=88)
+
+ # Article has an image, so it goes out as a photo message.
+ caption = photo.call_args.kwargs["caption"]
+ plain.assert_not_called()
+ self.assertIn(f'href="https://news.example.de/admin/articles/{article_id}"', caption)
+ self.assertIn('href="https://news.example.de/admin/freigabe"', caption)
+ finally:
+ os.environ.pop("PORTAL_BASE_URL", None)
+ config_module.get_settings.cache_clear()
+
+ def test_pipeline_summary_links_to_the_queue_when_something_waits(self) -> None:
+ from backend.app import telegram_bot
+
+ os.environ["PORTAL_BASE_URL"] = "https://news.example.de"
+ config_module.get_settings.cache_clear()
+ try:
+ with patch.object(telegram_bot, "send_message") as plain:
+ telegram_bot.notify_pipeline_done({"ingested": 3, "processed": 3, "pending_review": 2})
+ text = plain.call_args.args[0]
+ self.assertIn('href="https://news.example.de/admin/freigabe"', text)
+
+ with patch.object(telegram_bot, "send_message") as plain:
+ telegram_bot.notify_pipeline_done({"ingested": 0, "processed": 0, "pending_review": 0})
+ self.assertNotIn("/admin/freigabe", plain.call_args.args[0])
+ finally:
+ os.environ.pop("PORTAL_BASE_URL", None)
+ config_module.get_settings.cache_clear()
+
+
class TestLegacyArticlesKeepPublishing(EditorialReviewTestBase):
def test_approved_article_without_stamp_still_publishes(self) -> None:
"""Altbestand: approved before the gate existed, must not get stuck."""
diff --git a/docs/AUTOMATION.md b/docs/AUTOMATION.md
index 776cf9c..46c4f86 100644
--- a/docs/AUTOMATION.md
+++ b/docs/AUTOMATION.md
@@ -104,10 +104,14 @@ Wenn ein Artikel umgeschrieben wurde und geprüft werden muss:
🟢 Relevanz-Score: 87/100
📄 312 Wörter
🏷 #VanLife #Camping #Wohnmobil
-🔗 Im Portal prüfen und freigeben
+🔗 Diesen Artikel prüfen
+📋 Alle wartenden Artikel
Erst nach der Freigabe geht der Beitrag nach WordPress und wird eingeplant.
```
+Zwei Links: der erste springt direkt in den gemeldeten Artikel, der zweite auf
+die Freigabe-Warteschlange `/admin/freigabe`, wenn mehrere Artikel anstehen.
+
Diese Nachricht trägt **bewusst keinen Freigabe-Button**: Der Sinn des Gates ist,
dass der Artikel gelesen wurde. Freigeben, neu schreiben und verwerfen passiert
auf der Artikelseite im Portal, einen Tipp auf den Link entfernt.