---
title: "News-Kingdom-Architektur"
description: "Wie ein Beitrag aus dem Blog eines Mitglieds zur Story auf der Startseite wird. KI-Bewertung, bezahlte Promotion über PaymentConductor, Freigabe durch Admins in Telegram, JSONB-Newstabellen und das Audit-Protokoll der Einreichungen."
locale: "de"
---
# News-Kingdom-Architektur

> **Info**
> Nutze die Tabs **Gründer** / **Entwickler** in der Seitenleiste der Dokumentation, um diese Seite zu filtern. Gründer sehen den Geschäftsablauf und die Stellschrauben für Betreiber. Entwickler sehen die Services, Routen, Umgebungsvariablen und einen lokalen Testlauf.

News Kingdom ist die Promotion-Schicht über dem News-Modul von Ring. Mitglieder mit der Rolle `member` oder höher schreiben Artikel, die in ihrem persönlichen Bereich unter `/{username}/{slug}` liegen. Danach kann ein Autor einen Platz im Haupt-Feed `/news` beantragen. Ring bewertet den Artikel, verlangt vom Autor einen Preis, der sich nach dieser Bewertung richtet, und schickt ihn an die Plattform-Admins in Telegram, die endgültig Ja oder Nein sagen.

Jede Promotion-Anfrage durchläuft ein einziges Feld des Artikels, `mainPageStatus`:

| Phase | Was passiert | `mainPageStatus` |
|-------|--------------|------------------|
| Einreichen | Der Autor beantragt einen Platz auf der Startseite | `submitted` |
| Bewerten | KI oder Heuristik bewertet Ethik, Spam, Duplikate und Qualität | `ai_scored` |
| Harte Sperre | Ethik zu niedrig, Spam- oder Duplikatrisiko zu hoch | `published_blog_only` |
| Checkout | Der Preis steht fest und ein PaymentConductor-Checkout öffnet sich | `payment_pending` |
| Bezahlt | Der Webhook bestätigt die Zahlung, die Admins erhalten eine Telegram-Karte | `awaiting_admin_approval` |
| Freigegeben | Der Artikel erscheint seitenweit im Haupt-Feed | `published_main` |
| Abgelehnt | Ein Admin lehnt ab. Der Artikel bleibt im Blog des Autors | `rejected` |

### For founders

## Warum das für deinen Klon wichtig ist

- **Mitglieder liefern die Inhalte und bezahlen für Reichweite.** Du betreibst eine Zeitung, ohne eine Redaktion einzustellen. Autoren, die auf die Titelseite wollen, zahlen dafür, und die letzte redaktionelle Entscheidung bleibt bei dir.
- **Schlechtes wird zuerst aussortiert.** Spam, Duplikate und Beiträge mit niedriger Ethik werden durch die Bewertung gestoppt, bevor jemand zahlt und bevor ein Admin Zeit damit verbringt.
- **Du gibst vom Smartphone aus frei.** Jede bezahlte Einreichung erreicht deine Admins als Telegram-Nachricht mit den Buttons Approve, Reject, View und Counter.
- **Gute Storys zahlen sich aus.** Wenn ein Admin eine von einem Mitglied geschriebene Story freigibt, stellt Ring eine Prämiengutschrift für den Autor in die Warteschlange und startet Übersetzungen in deine anderen Sprachen.

  
- **[Mitglieder-Blogs](/docs/features/member-blog.md)** — Wo der Artikel vor der Promotion liegt: die persönliche URL des Autors.

  
- **[News-Modul](/docs/features/news.md)** — Der Haupt-Feed, Kategorien, Likes und der Editor, den Autoren nutzen.

  
- **[PaymentConductor](/docs/features/payment-conductor.md)** — Der Checkout, der die Promotion-Gebühr über deinen konfigurierten Zahlungsanbieter abrechnet.

  
- **[Verwaltung über Telegram](/docs/features/manage-via-telegram.md)** — Der Admin-Bot mit den Freigabe-Buttons.

```mermaid
flowchart LR
  A["Mitgliederartikel unter /username/slug"] --> B["Promotion beantragen"]
  B --> C{"KI-Bewertung"}
  C -->|harte Sperre| D["Bleibt nur im Blog"]
  C -->|ok| E["Promotion-Preis bezahlen"]
  E --> F["Telegram-Karte an Admins"]
  F -->|Approve| G["Haupt-Feed /news"]
  F -->|Reject| H["Bleibt im Blog"]
  F -->|Counter| E
```

### Typische Szenarien

1. **Lokalzeitung.** Ein Stadt-Klon lässt lokale Mitglieder in ihren Blogs posten und verlangt eine kleine Gebühr für Storys auf der Titelseite. Die Admins geben die besten in Telegram frei.
2. **Expertennetzwerk.** Ein Fach-Klon hält die Qualitätsmesslatte hoch: starke Artikel bekommen einen niedrigen Preis, schwache werden durch den Preis ausgeschlossen.
3. **Moderierter Kampagnen-Hub.** Ein Klon einer Bewegung setzt auf die Regeln der harten Sperre, damit Spam nie die Admin-Warteschlange erreicht.

### Stellschrauben für Betreiber

Du legst die Preise in UAH über Server-Umgebungsvariablen fest. Wenn du sie nicht setzt, gelten diese Standardwerte:

| Einstellung | Standard | Wirkung |
|---------|---------|--------|
| Grundpreis | 50 UAH | Mindestpreis für einen Artikel mit hoher Qualität |
| Skala | 2000 UAH | Wie stark der Preis steigt, wenn die Qualität sinkt |
| Maximum | 5000 UAH | Preisobergrenze |
| Untergrenze für niedrige Qualität | Qualität unter 0.25 | Der Preis wird auf mindestens 4999 UAH angehoben |
| Gegenangebot des Admins | +25% | Der Telegram-Button **Counter** legt einen neuen Betrag fest, den der Autor zahlen muss |

### For developers

## Modulübersicht (geprüft in `ring/web`)

| Bereich | Pfad |
|---------|------|
| Zustandsautomat, Audit, Freigabe und Ablehnung | `features/news/services/news-promotion-workflow.ts` |
| OpenRouter-Bewertung und Preis | `features/news/services/news-scoring-service.ts` |
| Duplikat-Heuristik | `features/news/services/news-duplicate-check.ts` |
| Checkout-Fassade (`purpose: 'news_promotion'`) | `features/news/services/news-payment-service.ts` |
| Webhook-Handler für Zahlungen | `lib/payments/conductor/handlers/news-promotion.ts`, `news-promotion-paypal.ts` |
| Admin-Karte und Tastatur in Telegram | `features/news/services/news-telegram-approval.ts` |
| Verarbeitung von Telegram-Callbacks | `lib/telegram/admin-bot/news-callback-handler.ts` (eingebunden über `app/api/telegram/admin-bot/webhook/route.ts`) |
| Promotion-UI für Mitglieder | `features/news/components/news-promotion-panel.tsx` auf `/my-news` |
| Status- und Feldtypen | `features/news/types/index.ts` (`MainPageStatus`, `NewsContentType`, `NewsVisibility`) |
| JSONB-Zeilen-Mapper | `lib/news/map-news-document.ts` |
| Branding und absolute URLs | `lib/site-branding.ts` (`localeBlogArticleUrl`, `absoluteSiteUrl`) |

## Routen

| Methode und Pfad | Authentifizierung | Zweck |
|-----------------|------|------|
| `POST /api/news/promotion/submit` | Session, nur der Autor des Artikels | Setzt `promoteToMainPage`, bewertet, öffnet den Checkout |
| `PATCH /api/news/promotion/submit` | Header `x-news-promotion-secret` | Dev oder Test: als bezahlt markieren und Admins benachrichtigen |
| `POST /api/payments/{wayforpay,stripe,paypal}/webhook` | Signatur des Zahlungsanbieters | Kanonische Zahlungsbestätigung |
| `POST /api/news/promotion/wayforpay-webhook` | Payload des Zahlungsanbieters | Veraltete Weiterleitung an den kanonischen WayForPay-Webhook |
| `POST /api/mcp/v1/news/[id]/{score,approve,reject,publish}` | Service-Token (`withMcpGuard`) | Agenten- und MCP-Operationen auf demselben Workflow |

## Bewertungsregeln

`scoreNewsForMainPage()` ruft OpenRouter auf, wenn `OPENROUTER_API_KEY` gesetzt ist. Ohne Schlüssel bleiben die heuristischen Standardwerte (ethics 0.7, spam 0.2, merit 0.5) bestehen und es wird `model: 'heuristic'` gespeichert.

- **Duplikatrisiko:** 0.95, wenn ein anderer Artikel denselben `slug` oder `siteWideSlug` hat. 0.9, wenn ein veröffentlichter Artikel (unter den ersten 200) denselben Titel hat.
- **Harte Sperre:** `ethics < NEWS_ETHICS_MIN` oder `spamRisk > NEWS_SPAM_MAX` oder `duplicateRisk > NEWS_DUPLICATE_MAX`. Ergebnis: `published_blog_only` und kein Checkout.
- **Preis:** `clamp(base, max, base + (1 - merit) * scale)`. Eine Qualität unter `NEWS_MERIT_SOFT_REJECT` hebt den Preis auf mindestens 4999 an. Niedrige Qualität ändert den Status nicht.

## Umgebungsvariablen

Keiner der `NEWS_*`-Schlüssel steht in `env.local.template`. Der Code liest sie mit diesen Standardwerten.

| Variable | Standard | Gelesen in |
|----------|---------|---------|
| `NEWS_PROMO_BASE_UAH` / `NEWS_PROMO_SCALE_UAH` / `NEWS_PROMO_MAX_UAH` | 50 / 2000 / 5000 | `news-scoring-service.ts` |
| `NEWS_ETHICS_MIN` / `NEWS_SPAM_MAX` / `NEWS_DUPLICATE_MAX` | 0.35 / 0.85 / 0.9 | `news-scoring-service.ts` |
| `NEWS_MERIT_SOFT_REJECT` | 0.25 | Bewertung und Workflow |
| `OPENROUTER_API_KEY`, `OPENROUTER_MODEL_PRIMARY`, `OPENROUTER_MODEL_FALLBACK` | nicht gesetzt | `news-scoring-service.ts` |
| `PAYMENT_NEWS_PROCESSOR` | fällt auf `PAYMENT_DEFAULT_PROCESSOR` zurück, dann auf `wayforpay` | `lib/payments/payment.config.ts` |
| `NEWS_PROMOTION_WEBHOOK_SECRET` | nicht gesetzt | `PATCH /api/news/promotion/submit` |
| `ADMIN_BOT_TOKEN` | nicht gesetzt | Antworten auf Telegram-Callbacks |

## Daten

Alle vier Tabellen nutzen das JSONB-Dokumentmodell (`id`, `data jsonb`, Zeitstempel) in `data/schema.sql` (abgeflacht, v4.1.0): `news`, `news_categories`, `news_likes`, `news_submission_audit`. Jeder Übergang hängt einen Eintrag an `data.mainPageStatusHistory` an und schreibt eine Zeile in `news_submission_audit`. `data/migrations/002_news_content_schema.sql` und `003_news_kingdom_upgrade.sql` sind historisch. Neuinstallationen wenden nur `schema.sql` an.

## Nebenwirkungen der Freigabe

`approveMainPagePublication()` setzt `status: 'published'` und `visibility: 'site-wide'`. Bei von Nutzern verfassten Storys stellt es anschließend `enqueueRewardCreditAddEvent({ trigger: 'newsStoryApproved' })` in die Warteschlange und startet `generateArticleTranslations()`. Empfänger in Telegram sind Nutzer mit einer Plattform-Admin-Rolle und einer `communication.telegramId`.

## Lokaler Testlauf

**Schema anwenden und einen Mitgliederartikel anlegen**

Wende `data/schema.sql` an und melde dich als Nutzer mit der Rolle `member` oder höher an. Lege unter `/news/create` einen Artikel an. Er erhält `blogUsername` und erscheint nach der Veröffentlichung unter `/{username}/{slug}`.

**Zur Promotion einreichen**

{`curl -X POST http://localhost:3000/api/news/promotion/submit \\
  -H "Content-Type: application/json" \\
  -b "" \\
  -d '{"articleId":""}'`}

Die Antwort enthält `status`, `aiScore` und entweder eine Weiterleitung oder Zahlungsfelder für den Checkout.

**Zahlung in Dev simulieren**

{`curl -X PATCH http://localhost:3000/api/news/promotion/submit \\
  -H "Content-Type: application/json" \\
  -H "x-news-promotion-secret: $NEWS_PROMOTION_WEBHOOK_SECRET" \\
  -d '{"articleId":"","payment":{}}'`}

**Freigeben**

Drücke **Approve** auf der Telegram-Karte oder rufe `POST /api/mcp/v1/news//approve` mit einem Service-Token auf. Prüfe, dass `mainPageStatus` den Wert `published_main` hat und eine Zeile in `news_submission_audit` existiert.

## Bekannte Lücken

## Verwandte Dokumentation

  
- [features/member-blog](/docs/features/member-blog.md) — Gleicher Ablauf: wo der Artikel vor und nach einer Promotion-Anfrage liegt.

  
- [features/news](/docs/features/news.md) — Voraussetzung: das News-Modul, der Editor und der Haupt-Feed, in den News Kingdom befördert.

  
- [architecture/payment-conductor](/docs/architecture/payment-conductor.md) — Abhängigkeit: wie der news_promotion-Checkout und seine Webhooks geroutet werden.

  
- [features/manage-via-telegram](/docs/features/manage-via-telegram.md) — Siehe auch: der Admin-Bot mit den Buttons Approve, Reject und Counter.
