Commits vergleichen
3 Commits
acb3c6a6cb
...
main
| Autor | SHA1 | Datum | |
|---|---|---|---|
|
|
6b4af4cf2a | ||
|
|
17088e588f | ||
|
|
97997724de |
@@ -415,12 +415,14 @@ async def create_source(
|
|||||||
"""Neue Quelle hinzufuegen (org-spezifisch)."""
|
"""Neue Quelle hinzufuegen (org-spezifisch)."""
|
||||||
tenant_id = current_user.get("tenant_id")
|
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
|
domain = data.domain
|
||||||
|
if not domain and data.url:
|
||||||
|
domain = _extract_domain(data.url)
|
||||||
if domain:
|
if domain:
|
||||||
domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower())
|
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:
|
if data.url:
|
||||||
cursor = await db.execute(
|
cursor = await db.execute(
|
||||||
"SELECT id, name FROM sources WHERE url = ? AND status = 'active'",
|
"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']})",
|
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(
|
cursor = await db.execute(
|
||||||
"""INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id)
|
"""INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||||
|
|||||||
@@ -5338,13 +5338,14 @@ body.tutorial-active .tutorial-cursor {
|
|||||||
|
|
||||||
/* ===== Credits-Anzeige im User-Dropdown ===== */
|
/* ===== Credits-Anzeige im User-Dropdown ===== */
|
||||||
.credits-section {
|
.credits-section {
|
||||||
padding: 8px 16px 12px;
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-divider {
|
.credits-divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
margin-bottom: 10px;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-label {
|
.credits-label {
|
||||||
@@ -5352,22 +5353,24 @@ body.tutorial-active .tutorial-cursor {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-tertiary);
|
||||||
margin-bottom: 6px;
|
margin-bottom: 8px;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-bar-container {
|
.credits-bar-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 6px;
|
height: 8px;
|
||||||
background: var(--bg-tertiary);
|
background: rgba(255,255,255,0.08);
|
||||||
border-radius: 3px;
|
border: 1px solid rgba(255,255,255,0.12);
|
||||||
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-bar {
|
.credits-bar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
transition: width 0.6s ease, background-color 0.3s ease;
|
transition: width 0.6s ease, background-color 0.3s ease;
|
||||||
min-width: 2px;
|
min-width: 2px;
|
||||||
@@ -5383,13 +5386,18 @@ body.tutorial-active .tutorial-cursor {
|
|||||||
|
|
||||||
.credits-info {
|
.credits-info {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-tertiary);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
gap: 4px;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-info span {
|
.credits-info span {
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
color: var(--text-primary);
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.credits-percent {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
<div id="credits-bar" class="credits-bar"></div>
|
<div id="credits-bar" class="credits-bar"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="credits-info">
|
<div class="credits-info">
|
||||||
<span id="credits-remaining">0</span> von <span id="credits-total">0</span>
|
<span><span id="credits-remaining">0</span> von <span id="credits-total">0</span></span>
|
||||||
|
<span class="credits-percent" id="credits-percent"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -490,6 +490,8 @@ const App = {
|
|||||||
} else if (percentUsed > 50) {
|
} else if (percentUsed > 50) {
|
||||||
bar.classList.add('warning');
|
bar.classList.add('warning');
|
||||||
}
|
}
|
||||||
|
const percentEl = document.getElementById("credits-percent");
|
||||||
|
if (percentEl) percentEl.textContent = percentRemaining.toFixed(0) + "% verbleibend";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dropdown Toggle
|
// Dropdown Toggle
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren