Quellenverwaltung: Boulevard-Kategorie, Duplikat-Prüfung, Domain-Normalisierung
- Boulevard als Kategorie in HTML-Dropdowns, JS-Labels und Pydantic-Validierung - create_source: URL-Duplikat-Prüfung (409 Conflict bei existierender URL) - create_source + update_source: Domain via _DOMAIN_ALIASES normalisieren - System-Quellen (auto-entdeckt) sind jetzt von allen Nutzern editierbar Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -96,7 +96,7 @@ class SourceCreate(BaseModel):
|
||||
url: Optional[str] = None
|
||||
domain: Optional[str] = None
|
||||
source_type: str = Field(default="rss_feed", pattern="^(rss_feed|web_source|excluded)$")
|
||||
category: str = Field(default="sonstige", pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|sonstige)$")
|
||||
category: str = Field(default="sonstige", pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|boulevard|sonstige)$")
|
||||
status: str = Field(default="active", pattern="^(active|inactive)$")
|
||||
notes: Optional[str] = None
|
||||
|
||||
@@ -106,7 +106,7 @@ class SourceUpdate(BaseModel):
|
||||
url: Optional[str] = None
|
||||
domain: Optional[str] = None
|
||||
source_type: Optional[str] = Field(default=None, pattern="^(rss_feed|web_source|excluded)$")
|
||||
category: Optional[str] = Field(default=None, pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|sonstige)$")
|
||||
category: Optional[str] = Field(default=None, pattern="^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|boulevard|sonstige)$")
|
||||
status: Optional[str] = Field(default=None, pattern="^(active|inactive)$")
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from models import SourceCreate, SourceUpdate, SourceResponse, DiscoverRequest, DiscoverResponse, DiscoverMultiResponse, DomainActionRequest
|
||||
from auth import get_current_user
|
||||
from database import db_dependency, refresh_source_counts
|
||||
from source_rules import discover_source, discover_all_feeds, evaluate_feeds_with_claude, _extract_domain, _detect_category, domain_to_display_name
|
||||
from source_rules import discover_source, discover_all_feeds, evaluate_feeds_with_claude, _extract_domain, _detect_category, domain_to_display_name, _DOMAIN_ALIASES
|
||||
import aiosqlite
|
||||
|
||||
logger = logging.getLogger("osint.sources")
|
||||
@@ -16,14 +16,13 @@ SOURCE_UPDATE_COLUMNS = {"name", "url", "domain", "source_type", "category", "st
|
||||
|
||||
|
||||
def _check_source_ownership(source: dict, username: str):
|
||||
"""Prueft ob der Nutzer die Quelle bearbeiten/loeschen darf."""
|
||||
"""Prueft ob der Nutzer die Quelle bearbeiten/loeschen darf.
|
||||
|
||||
System-Quellen (auto-entdeckt) duerfen von jedem bearbeitet werden.
|
||||
Nutzer-Quellen nur vom Ersteller.
|
||||
"""
|
||||
added_by = source.get("added_by", "")
|
||||
if added_by == "system":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="System-Quellen koennen nicht veraendert werden",
|
||||
)
|
||||
if added_by and added_by != username:
|
||||
if added_by and added_by != "system" and added_by != username:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Nur der Ersteller kann diese Quelle bearbeiten",
|
||||
@@ -442,13 +441,32 @@ async def create_source(
|
||||
):
|
||||
"""Neue Quelle hinzufuegen (org-spezifisch)."""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
|
||||
# Domain normalisieren (Subdomain-Aliase auflösen)
|
||||
domain = data.domain
|
||||
if domain:
|
||||
domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower())
|
||||
|
||||
# Duplikat-Prüfung: gleiche URL bereits vorhanden?
|
||||
if data.url:
|
||||
cursor = await db.execute(
|
||||
"SELECT id, name FROM sources WHERE url = ? AND status = 'active'",
|
||||
(data.url,),
|
||||
)
|
||||
existing = await cursor.fetchone()
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})",
|
||||
)
|
||||
|
||||
cursor = await db.execute(
|
||||
"""INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(
|
||||
data.name,
|
||||
data.url,
|
||||
data.domain,
|
||||
domain,
|
||||
data.source_type,
|
||||
data.category,
|
||||
data.status,
|
||||
@@ -483,6 +501,9 @@ async def update_source(
|
||||
for field, value in data.model_dump(exclude_none=True).items():
|
||||
if field not in SOURCE_UPDATE_COLUMNS:
|
||||
continue
|
||||
# Domain normalisieren
|
||||
if field == "domain" and value:
|
||||
value = _DOMAIN_ALIASES.get(value.lower(), value.lower())
|
||||
updates[field] = value
|
||||
|
||||
if not updates:
|
||||
|
||||
@@ -420,6 +420,7 @@
|
||||
<option value="think-tank">Think Tank</option>
|
||||
<option value="international">International</option>
|
||||
<option value="regional">Regional</option>
|
||||
<option value="boulevard">Boulevard</option>
|
||||
<option value="sonstige">Sonstige</option>
|
||||
</select>
|
||||
<label for="sources-search" class="sr-only">Quellen durchsuchen</label>
|
||||
@@ -475,6 +476,7 @@
|
||||
<option value="think-tank">Think Tank</option>
|
||||
<option value="international">International</option>
|
||||
<option value="regional">Regional</option>
|
||||
<option value="boulevard">Boulevard</option>
|
||||
<option value="sonstige" selected>Sonstige</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -506,6 +506,7 @@ const UI = {
|
||||
'think-tank': 'Think Tank',
|
||||
'international': 'Intl.',
|
||||
'regional': 'Regional',
|
||||
'boulevard': 'Boulevard',
|
||||
'sonstige': 'Sonstige',
|
||||
},
|
||||
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren