Browse Source

Fix cross-workspace feed bleed and make cron workspace-aware

Feed aggregator:
- fetchAllFeeds() now queries the workspaces collection to get all
  workspace IDs and fetches feeds for each one, so all workspaces
  get their feeds auto-refreshed every 5 minutes (not just 'default')
- Legacy feed filter (workspaceId $exists false) is now only applied
  when querying the 'default' workspace, preventing old un-stamped
  feed items from bleeding into workspace 2, 3, etc.

Settings UI:
- Page heading now shows the active workspace name ("Configuring
  workspace: X") so users know which workspace they are configuring
  platform connections for — makes it obvious that each workspace
  requires its own OAuth flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Benjamin Harris 3 tuần trước cách đây
mục cha
commit
4c25398e2e
4 tập tin đã thay đổi với 36 bổ sung12 xóa
  1. 30 12
      services/feed-aggregator/index.js
  2. 1 0
      ui/src/locales/en.ts
  3. 1 0
      ui/src/locales/tr.ts
  4. 4 0
      ui/src/views/Settings.vue

+ 30 - 12
services/feed-aggregator/index.js

@@ -46,19 +46,35 @@ async function fetchPlatformFeed(platform, serviceUrl, workspaceId = 'default')
   }
 }
 
-async function fetchAllFeeds() {
-  log.info({ action: 'feed_fetch_all' }, 'Fetching feeds from all platforms');
+// Resolve all workspace IDs so the cron refreshes feeds for every workspace,
+// not just 'default'. Falls back to ['default'] if the DB is unavailable.
+async function getAllWorkspaceIds() {
+  try {
+    const db = await getDb();
+    const rows = await db.collection('workspaces').find({}, { projection: { _id: 1 } }).toArray();
+    const ids = rows.map((r) => r._id).filter(Boolean);
+    return ids.length ? ids : ['default'];
+  } catch {
+    return ['default'];
+  }
+}
 
-  const results = await Promise.allSettled(
-    Object.entries(PLATFORM_SERVICES).map(([platform, url]) =>
-      fetchPlatformFeed(platform, url)
-    )
-  );
+async function fetchAllFeeds() {
+  const workspaceIds = await getAllWorkspaceIds();
+  log.info({ action: 'feed_fetch_all', workspaces: workspaceIds.length }, 'Fetching feeds for all workspaces');
 
   const summary = {};
-  Object.keys(PLATFORM_SERVICES).forEach((platform, i) => {
-    summary[platform] = results[i].status === 'fulfilled' ? results[i].value.length : 0;
-  });
+  for (const wsId of workspaceIds) {
+    const results = await Promise.allSettled(
+      Object.entries(PLATFORM_SERVICES).map(([platform, url]) =>
+        fetchPlatformFeed(platform, url, wsId)
+      )
+    );
+    Object.keys(PLATFORM_SERVICES).forEach((platform, i) => {
+      const count = results[i].status === 'fulfilled' ? results[i].value.length : 0;
+      summary[`${wsId}:${platform}`] = count;
+    });
+  }
 
   log.info({ action: 'feed_fetch_all', outcome: 'success', summary });
   return summary;
@@ -85,8 +101,10 @@ app.get('/feeds', async (request) => {
   const db = await getDb();
   const col = db.collection('feeds');
 
-  // Include legacy items without workspaceId (backwards compat)
-  const filter = { $or: [{ workspaceId }, { workspaceId: { $exists: false } }] };
+  // Legacy items (no workspaceId field) only visible in the default workspace to
+  // avoid cross-workspace feed bleed.
+  const legacyClause = workspaceId === 'default' ? [{ workspaceId: { $exists: false } }] : [];
+  const filter = { $or: [{ workspaceId }, ...legacyClause] };
   if (platform) filter.platform = platform;
   if (tag) filter.tags = tag;
 

+ 1 - 0
ui/src/locales/en.ts

@@ -315,6 +315,7 @@ export default {
 
   settings: {
     title: 'Platform Connections',
+    workspaceScope: 'Configuring workspace:',
     subtitle: 'Edit the {env} file to connect platforms, then restart the relevant service.',
     connected: 'Connected',
     notConnected: 'Not connected',

+ 1 - 0
ui/src/locales/tr.ts

@@ -315,6 +315,7 @@ export default {
 
   settings: {
     title: 'Platform Bağlantıları',
+    workspaceScope: 'Yapılandırılan çalışma alanı:',
     subtitle: 'Platforma bağlanmak için {env} dosyasını düzenle, ardından ilgili servisi yeniden başlat.',
     connected: 'Bağlı',
     notConnected: 'Bağlı değil',

+ 4 - 0
ui/src/views/Settings.vue

@@ -4,6 +4,10 @@
 
       <div>
         <h1 class="text-2xl font-bold mb-1">{{ $t('settings.title') }}</h1>
+        <p class="text-sm text-gray-500 mt-1">
+          {{ $t('settings.workspaceScope') }}
+          <span class="text-violet-400 font-medium">{{ workspaceStore.activeWorkspace?.name || workspaceStore.activeWorkspaceId }}</span>
+        </p>
       </div>
 
       <!-- ═══════════════════════════════════════════════════════════════════