about summary refs log tree commit diff
path: root/streaming
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2017-11-18 00:16:48 +0100
committerGitHub <noreply@github.com>2017-11-18 00:16:48 +0100
commit24cafd73a2b644025e9aeaadf4fed46dd3ecea4d (patch)
treee0a0ad775612644d29193e81a9326f0e4c21d6af /streaming
parent4a2fc2d444a80050ad9ba5e83aa5e69d3148ab95 (diff)
Lists (#5703)
* Add structure for lists

* Add list timeline streaming API

* Add list APIs, bind list-account relation to follow relation

* Add API for adding/removing accounts from lists

* Add pagination to lists API

* Add pagination to list accounts API

* Adjust scopes for new APIs

- Creating and modifying lists merely requires "write" scope
- Fetching information about lists merely requires "read" scope

* Add test for wrong user context on list timeline

* Clean up tests
Diffstat (limited to 'streaming')
-rw-r--r--streaming/index.js50
1 files changed, 49 insertions, 1 deletions
diff --git a/streaming/index.js b/streaming/index.js
index 83903b89b..f0b8ce007 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -254,6 +254,26 @@ const startWorker = (workerId) => {
 
   const placeholders = (arr, shift = 0) => arr.map((_, i) => `$${i + 1 + shift}`).join(', ');
 
+  const authorizeListAccess = (id, req, next) => {
+    pgPool.connect((err, client, done) => {
+      if (err) {
+        next(false);
+        return;
+      }
+
+      client.query('SELECT id, account_id FROM lists WHERE id = $1 LIMIT 1', [id], (err, result) => {
+        done();
+
+        if (err || result.rows.length === 0 || result.rows[0].account_id !== req.accountId) {
+          next(false);
+          return;
+        }
+
+        next(true);
+      });
+    });
+  };
+
   const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false, notificationOnly = false) => {
     const streamType = notificationOnly ? ' (notification)' : '';
     log.verbose(req.requestId, `Starting stream from ${id} for ${req.accountId}${streamType}`);
@@ -410,7 +430,22 @@ const startWorker = (workerId) => {
     streamFrom(`timeline:hashtag:${req.query.tag.toLowerCase()}:local`, req, streamToHttp(req, res), streamHttpEnd(req), true);
   });
 
-  const wss    = new WebSocket.Server({ server, verifyClient: wsVerifyClient });
+  app.get('/api/v1/streaming/list', (req, res) => {
+    const listId = req.query.list;
+
+    authorizeListAccess(listId, req, authorized => {
+      if (!authorized) {
+        res.writeHead(404, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ error: 'Not found' }));
+        return;
+      }
+
+      const channel = `timeline:list:${listId}`;
+      streamFrom(channel, req, streamToHttp(req, res), streamHttpEnd(req, subscriptionHeartbeat(channel)));
+    });
+  });
+
+  const wss = new WebSocket.Server({ server, verifyClient: wsVerifyClient });
 
   wss.on('connection', ws => {
     const req      = ws.upgradeReq;
@@ -443,6 +478,19 @@ const startWorker = (workerId) => {
     case 'hashtag:local':
       streamFrom(`timeline:hashtag:${location.query.tag.toLowerCase()}:local`, req, streamToWs(req, ws), streamWsEnd(req, ws), true);
       break;
+    case 'list':
+      const listId = location.query.list;
+
+      authorizeListAccess(listId, req, authorized => {
+        if (!authorized) {
+          ws.close();
+          return;
+        }
+
+        const channel = `timeline:list:${listId}`;
+        streamFrom(channel, req, streamToWs(req, ws), streamWsEnd(req, ws, subscriptionHeartbeat(channel)));
+      });
+      break;
     default:
       ws.close();
     }