Per-User Domain-Ausschlüsse + Grundquellen-Schutz

- Neue Tabelle user_excluded_domains für benutzerspezifische Ausschlüsse
- Domain-Ausschlüsse wirken nur für den jeweiligen User, nicht org-weit
- user_id wird durch die gesamte Pipeline geschleust (Orchestrator → Researcher → RSS-Parser)
- Grundquellen (is_global) können nicht mehr bearbeitet/gelöscht werden im Frontend
- Grundquelle-Badge bei globalen Quellen statt Edit/Delete-Buttons
- Filter Von mir ausgeschlossen im Quellen-Modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-03-08 14:30:21 +01:00
Ursprung 18954cf70e
Commit 5e19736a25
13 geänderte Dateien mit 149 neuen und 108 gelöschten Zeilen

Datei anzeigen

@@ -425,7 +425,7 @@ const App = {
_currentUsername: '',
_allSources: [],
_sourcesOnly: [],
_blacklistOnly: [],
_myExclusions: [], // [{domain, notes, created_at}]
_expandedGroups: new Set(),
_editingSourceId: null,
_timelineFilter: 'all',
@@ -2173,13 +2173,14 @@ const App = {
async loadSources() {
try {
const [sources, stats] = await Promise.all([
const [sources, stats, myExclusions] = await Promise.all([
API.listSources(),
API.getSourceStats(),
API.getMyExclusions(),
]);
this._allSources = sources;
this._sourcesOnly = sources.filter(s => s.source_type !== 'excluded');
this._blacklistOnly = sources.filter(s => s.source_type === 'excluded');
this._myExclusions = myExclusions || [];
this.renderSourceStats(stats);
this.renderSourceList();
@@ -2194,7 +2195,7 @@ const App = {
const rss = stats.by_type.rss_feed || { count: 0, articles: 0 };
const web = stats.by_type.web_source || { count: 0, articles: 0 };
const excluded = this._blacklistOnly.length;
const excluded = this._myExclusions.length;
bar.innerHTML = `
<span class="sources-stat-item"><span class="sources-stat-value">${rss.count}</span> RSS-Feeds</span>
@@ -2221,12 +2222,12 @@ const App = {
const excludedDomains = new Set();
const excludedNotes = {};
// Blacklist-Domains sammeln
this._blacklistOnly.forEach(s => {
const domain = (s.domain || s.name || '').toLowerCase();
// User-Ausschlüsse sammeln
this._myExclusions.forEach(e => {
const domain = (e.domain || '').toLowerCase();
if (domain) {
excludedDomains.add(domain);
excludedNotes[domain] = s.notes || '';
excludedNotes[domain] = e.notes || '';
}
});
@@ -2238,10 +2239,10 @@ const App = {
});
// Ausgeschlossene Domains die keine Feeds haben auch als Gruppe
this._blacklistOnly.forEach(s => {
const domain = (s.domain || s.name || '').toLowerCase();
this._myExclusions.forEach(e => {
const domain = (e.domain || '').toLowerCase();
if (domain && !groups.has(domain)) {
groups.set(domain, [s]);
groups.set(domain, []);
}
});
@@ -2249,6 +2250,7 @@ const App = {
let filteredGroups = [];
for (const [domain, feeds] of groups) {
const isExcluded = excludedDomains.has(domain);
const isGlobal = feeds.some(f => f.is_global);
// Typ-Filter
if (typeFilter === 'excluded' && !isExcluded) continue;
@@ -2271,7 +2273,7 @@ const App = {
if (!groupText.includes(search)) continue;
}
filteredGroups.push({ domain, feeds, isExcluded });
filteredGroups.push({ domain, feeds, isExcluded, isGlobal });
}
if (filteredGroups.length === 0) {
@@ -2286,7 +2288,7 @@ const App = {
});
list.innerHTML = filteredGroups.map(g =>
UI.renderSourceGroup(g.domain, g.feeds, g.isExcluded, excludedNotes[g.domain] || '')
UI.renderSourceGroup(g.domain, g.feeds, g.isExcluded, excludedNotes[g.domain] || '', g.isGlobal)
).join('');
// Erweiterte Gruppen wiederherstellen
@@ -2440,11 +2442,11 @@ const App = {
* Domain direkt ausschließen (aus der Gruppenliste).
*/
async blockDomainDirect(domain) {
if (!await confirmDialog(`"${domain}" wirklich ausschließen? Alle Feeds dieser Domain werden deaktiviert.`)) return;
if (!await confirmDialog(`"${domain}" wirklich ausschließen? Artikel dieser Domain werden bei deinen Recherchen ignoriert.`)) return;
try {
await API.blockDomain(domain);
UI.showToast(`${domain} gesperrt.`, 'success');
UI.showToast(`${domain} ausgeschlossen.`, 'success');
await this.loadSources();
this.updateSidebarStats();
} catch (err) {
@@ -2560,7 +2562,7 @@ const App = {
// Prüfen ob Domain ausgeschlossen ist
const inputDomain = url.replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0].toLowerCase();
const isBlocked = inputDomain && this._blacklistOnly.some(s => (s.domain || '').toLowerCase() === inputDomain);
const isBlocked = inputDomain && this._myExclusions.some(e => (e.domain || '').toLowerCase() === inputDomain);
if (isBlocked) {
if (!await confirmDialog(`"${inputDomain}" ist ausgeschlossen. Trotzdem hinzufügen? Der Ausschluss wird dabei aufgehoben.`)) return;