From 6b4af4cf2a94624fb6edfd937d107c37eb77ddaa Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Wed, 18 Mar 2026 00:13:36 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20justify-content:=20center=20=C3=BCberall?= =?UTF-8?q?=20wiederhergestellt=20+=20Quellen-Duplikatpr=C3=BCfung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CSS: 24x fälschliches flex-start zurück auf center (Login, Buttons, Modals, Badges, Map etc.) - Sources: Domain-Duplikatprüfung bei manuellem Hinzufügen (web_source 1x pro Domain, Domain aus URL extrahieren) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/routers/sources.py | 25 +++++++++++++++++++-- src/static/css/style.css | 48 ++++++++++++++++++++-------------------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/routers/sources.py b/src/routers/sources.py index e67edc8..f6318d1 100644 --- a/src/routers/sources.py +++ b/src/routers/sources.py @@ -415,12 +415,14 @@ async def create_source( """Neue Quelle hinzufuegen (org-spezifisch).""" tenant_id = current_user.get("tenant_id") - # Domain normalisieren (Subdomain-Aliase auflösen) + # Domain normalisieren (Subdomain-Aliase auflösen, aus URL extrahieren) domain = data.domain + if not domain and data.url: + domain = _extract_domain(data.url) if domain: domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower()) - # Duplikat-Prüfung: gleiche URL bereits vorhanden? + # Duplikat-Prüfung 1: gleiche URL bereits vorhanden? (tenant-übergreifend) if data.url: cursor = await db.execute( "SELECT id, name FROM sources WHERE url = ? AND status = 'active'", @@ -433,6 +435,25 @@ async def create_source( detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})", ) + # Duplikat-Prüfung 2: Domain bereits vorhanden? (tenant-übergreifend) + if domain: + cursor = await db.execute( + "SELECT id, name, source_type FROM sources WHERE LOWER(domain) = ? AND status = 'active' AND (tenant_id IS NULL OR tenant_id = ?) LIMIT 1", + (domain.lower(), tenant_id), + ) + domain_existing = await cursor.fetchone() + if domain_existing: + if data.source_type == "web_source": + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Web-Quelle für '{domain}' bereits vorhanden: {domain_existing['name']}", + ) + if not data.url: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Domain '{domain}' bereits als Quelle vorhanden: {domain_existing['name']}. Für einen neuen RSS-Feed bitte die Feed-URL angeben.", + ) + cursor = await db.execute( """INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", diff --git a/src/static/css/style.css b/src/static/css/style.css index c1a1ee8..c0b8469 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -235,7 +235,7 @@ a:hover { .login-container { display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; min-height: 100vh; padding: var(--sp-3xl); background: var(--bg-primary); @@ -356,7 +356,7 @@ a:hover { .btn { display: inline-flex; align-items: center; - justify-content: flex-start; + justify-content: center; gap: var(--sp-md); border: none; border-radius: var(--radius); @@ -1092,7 +1092,7 @@ a:hover { border-radius: var(--radius); display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; font-size: 12px; font-weight: 700; margin-top: 1px; @@ -1547,7 +1547,7 @@ a:hover { .vt-cluster-count { display: inline-flex; align-items: center; - justify-content: flex-start; + justify-content: center; min-width: 18px; height: 18px; padding: 0 5px; @@ -1720,7 +1720,7 @@ a:hover { backdrop-filter: blur(4px); z-index: 100; align-items: center; - justify-content: flex-start; + justify-content: center; } .modal-overlay.active { @@ -1857,7 +1857,7 @@ a:hover { display: flex; flex-direction: column; align-items: center; - justify-content: flex-start; + justify-content: center; padding: var(--sp-5xl) var(--sp-4xl); text-align: center; } @@ -1900,7 +1900,7 @@ a:hover { .loading-overlay { display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; gap: var(--sp-lg); padding: var(--sp-3xl); color: var(--text-secondary); @@ -1978,7 +1978,7 @@ a:hover { .progress-label-container { display: flex; - justify-content: flex-start; + justify-content: center; align-items: center; gap: var(--sp-md); position: relative; @@ -2642,7 +2642,7 @@ a:hover { border-radius: var(--radius); display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; transition: color 0.2s ease, background 0.2s ease; position: relative; } @@ -2667,7 +2667,7 @@ a:hover { border-radius: 8px; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; line-height: 1; pointer-events: none; animation: badgePop 0.3s ease; @@ -2769,7 +2769,7 @@ a:hover { border-radius: var(--radius); display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0; @@ -3421,7 +3421,7 @@ a:hover { .source-feed-count { display: inline-flex; align-items: center; - justify-content: flex-start; + justify-content: center; padding: 1px 8px; border-radius: 9px; font-size: 11px; @@ -3932,7 +3932,7 @@ select:focus-visible, textarea:focus-visible, border-radius: var(--radius); display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; transition: color 0.2s ease, background 0.2s ease; width: 36px; height: 36px; @@ -4348,7 +4348,7 @@ select:focus-visible, textarea:focus-visible, .map-empty { display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; height: 100%; color: var(--text-tertiary); font-size: 13px; @@ -4443,7 +4443,7 @@ a.map-popup-article:hover { border-radius: 50%; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; } .map-cluster span { font-family: var(--font-body); @@ -4539,7 +4539,7 @@ a.map-popup-article:hover { padding: 0; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; flex-shrink: 0; } .map-expand-btn:hover { @@ -4660,7 +4660,7 @@ a.map-popup-article:hover { z-index: 9999; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; box-shadow: 0 4px 16px rgba(0,0,0,0.3); transition: transform 0.2s, background 0.2s; } @@ -4727,7 +4727,7 @@ a.map-popup-article:hover { border-radius: 4px; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; } .chat-header-btn:hover { color: var(--text-primary); @@ -4819,7 +4819,7 @@ a.map-popup-article:hover { cursor: pointer; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; flex-shrink: 0; transition: background 0.15s; } @@ -4895,7 +4895,7 @@ a.map-popup-article:hover { .info-icon { display: inline-flex; align-items: center; - justify-content: flex-start; + justify-content: center; width: 16px; height: 16px; color: var(--text-disabled); @@ -5103,7 +5103,7 @@ a.map-popup-article:hover { cursor: pointer; display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; border-radius: var(--radius); transition: color 0.15s, background 0.15s; line-height: 1; @@ -5117,7 +5117,7 @@ a.map-popup-article:hover { .tutorial-bubble-dots { display: flex; gap: 5px; - justify-content: flex-start; + justify-content: center; margin-bottom: var(--sp-lg); flex-wrap: wrap; } @@ -5294,7 +5294,7 @@ body.tutorial-active .tutorial-cursor { background: rgba(0,0,0,0.6); display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; backdrop-filter: blur(2px); } .tutorial-resume-dialog { @@ -5315,7 +5315,7 @@ body.tutorial-active .tutorial-cursor { .tutorial-resume-actions { display: flex; gap: 12px; - justify-content: flex-start; + justify-content: center; } .tutorial-resume-actions .tutorial-btn { border: 1px solid var(--accent);