openvibers/openvibe.network Commits
Relay Feed search and examples through Network (#819)
Changes
@@@ -631,7 +631,10 @@ async function createFrameService(db, config, analytics, { privateKey = null, isif (kept && Date.now() - kept.at < 30_000) { res.set('Cache-Control', 'public, max-age=30'); return res.send(kept.body); }}const d = await getJson(u.href, auth ? { Authorization: auth, Accept: 'application/json' } : { Accept: 'application/json' });-- const body = JSON.stringify(d && Array.isArray(d.items) ? d : { items: [], next_cursor: null });++ if (path === '/api/v1/feed/search' && (!d || !Array.isArray(d.items))) return res.status(503).send('{"error":"feed search unavailable"}');++ const body = JSON.stringify(path === '/api/v1/feed/examples'++ ? (d && Array.isArray(d.examples) ? d : { examples: [] })++ : (d && Array.isArray(d.items) ? d : { items: [], next_cursor: null }));if (!auth && d) {if (relayed.size >= 500) relayed.clear();relayed.set(key, { at: Date.now(), body });@@@ -641,7 +644,7 @@ async function createFrameService(db, config, analytics, { privateKey = null, is}const qCursor = (v) => (/^[A-Za-z0-9._~=-]{1,300}$/.test(String(v || '')) ? String(v) : '');const qLimit = (v, max) => String(Math.min(Math.max(parseInt(v, 10) || 20, 1), max));-- router.options(['/feed', '/wall', '/status', '/posts', '/attachments'], (req, res) => {++ router.options(['/feed', '/feed/search', '/feed/examples', '/wall', '/status', '/posts', '/attachments'], (req, res) => {res.set({ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Authorization, Content-Type', 'Access-Control-Allow-Methods': 'GET, POST', 'Access-Control-Max-Age': '600' });res.status(204).end();});@@@ -696,6 +699,10 @@ async function createFrameService(db, config, analytics, { privateKey = null, isfollowing: /^(1|true)$/.test(String(req.query.following || '')) ? '1' : '',cursor: qCursor(req.query.cursor), limit: qLimit(req.query.limit, 40),}));++ router.get('/feed/search', (req, res) => relay(req, res, '/api/v1/feed/search', {++ q: qWords(req.query.q),++ }));++ router.get('/feed/examples', (req, res) => relay(req, res, '/api/v1/feed/examples', {}));router.get('/wall', (req, res) => relay(req, res, '/api/v1/pulse', {limit: qLimit(req.query.limit, 50), after: qCursor(req.query.after), cursor: qCursor(req.query.cursor),for: req.query.for === 'me' ? 'me' : '', origin: ['user', 'ai', 'system', 'all'].includes(String(req.query.origin || '')) ? String(req.query.origin) : '',@@@ -16,6 +16,8 @@ const { createFrameService } = require('../server/frame/service');res.setHeader('content-type', 'application/json');const u = new URL(req.url, 'http://x');if (u.pathname === '/api/v1/feed') return res.end(JSON.stringify({ items: [{ id: 'p:1', kind: u.searchParams.get('kind') || 'post', url: 'https://openvibe.community/p/1', title: 'A post' }], next_cursor: 'c2' }));++ if (u.pathname === '/api/v1/feed/search') return res.end(JSON.stringify({ items: [{ id: 'p:2', title: u.searchParams.get('q') }], next_cursor: null }));++ if (u.pathname === '/api/v1/feed/examples') return res.end(JSON.stringify({ examples: [{ text: 'New photos', reason: 'kind' }] }));if (u.pathname === '/api/v1/me/status' && req.method === 'POST') { let body = ''; req.on('data', (c) => { body += c; }); req.on('end', () => { asked.push({ url: req.url, auth: req.headers.authorization || '', body }); res.statusCode = req.headers.authorization ? 201 : 401; res.end(JSON.stringify({ ok: true })); }); return undefined; }if ((u.pathname === '/api/v1/spaces/status/attachments' || u.pathname === '/api/v1/posts') && req.method === 'POST') {const chunks = []; req.on('data', (c) => chunks.push(c));@@@ -42,9 +44,17 @@ const { createFrameService } = require('../server/frame/service');assert.strictEqual(d.next_cursor, 'c2');const first = new URL(asked[0].url, 'http://x');assert.deepStrictEqual([first.pathname, first.searchParams.get('kind'), first.searchParams.get('limit'), first.searchParams.get('cursor')], ['/api/v1/feed', 'clip', '12', 'c1']);++ ({ r, d } = await get('/feed/search?q=New%20photos'));++ assert.strictEqual(r.status, 200);++ assert.strictEqual(d.items[0].title, 'New photos');++ assert.strictEqual(new URL(asked[asked.length - 1].url, 'http://x').pathname, '/api/v1/feed/search');++ ({ r, d } = await get('/feed/examples'));++ assert.strictEqual(r.status, 200);++ assert.strictEqual(d.examples[0].text, 'New photos');++ const afterSearch = asked.length;({ d } = await get('/feed?kind=clip&limit=12&cursor=c1'));-- assert.strictEqual(asked.length, 1, 'the same anonymous page within 30 s is answered from memory');++ assert.strictEqual(asked.length, afterSearch, 'the same anonymous page within 30 s is answered from memory');({ r, d } = await get('/wall?limit=20&for=me', { Authorization: 'Bearer abcdefghijklmnop.qrstu', Cookie: 'ov_session=secret' }));const wall = asked[asked.length - 1];
@@ -631,7 +631,10 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
@@ -631,7 +631,10 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
if (kept && Date.now() - kept.at < 30_000) { res.set('Cache-Control', 'public, max-age=30'); return res.send(kept.body); }
if (kept && Date.now() - kept.at < 30_000) { res.set('Cache-Control', 'public, max-age=30'); return res.send(kept.body); }
}
}
const d = await getJson(u.href, auth ? { Authorization: auth, Accept: 'application/json' } : { Accept: 'application/json' });
const d = await getJson(u.href, auth ? { Authorization: auth, Accept: 'application/json' } : { Accept: 'application/json' });
- const body = JSON.stringify(d && Array.isArray(d.items) ? d : { items: [], next_cursor: null });
+ if (path === '/api/v1/feed/search' && (!d || !Array.isArray(d.items))) return res.status(503).send('{"error":"feed search unavailable"}');
+ const body = JSON.stringify(path === '/api/v1/feed/examples'
+ ? (d && Array.isArray(d.examples) ? d : { examples: [] })
+ : (d && Array.isArray(d.items) ? d : { items: [], next_cursor: null }));
if (!auth && d) {
if (!auth && d) {
if (relayed.size >= 500) relayed.clear();
if (relayed.size >= 500) relayed.clear();
relayed.set(key, { at: Date.now(), body });
relayed.set(key, { at: Date.now(), body });
@@ -641,7 +644,7 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
@@ -641,7 +644,7 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
}
}
const qCursor = (v) => (/^[A-Za-z0-9._~=-]{1,300}$/.test(String(v || '')) ? String(v) : '');
const qCursor = (v) => (/^[A-Za-z0-9._~=-]{1,300}$/.test(String(v || '')) ? String(v) : '');
const qLimit = (v, max) => String(Math.min(Math.max(parseInt(v, 10) || 20, 1), max));
const qLimit = (v, max) => String(Math.min(Math.max(parseInt(v, 10) || 20, 1), max));
- router.options(['/feed', '/wall', '/status', '/posts', '/attachments'], (req, res) => {
+ router.options(['/feed', '/feed/search', '/feed/examples', '/wall', '/status', '/posts', '/attachments'], (req, res) => {
res.set({ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Authorization, Content-Type', 'Access-Control-Allow-Methods': 'GET, POST', 'Access-Control-Max-Age': '600' });
res.set({ 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Authorization, Content-Type', 'Access-Control-Allow-Methods': 'GET, POST', 'Access-Control-Max-Age': '600' });
res.status(204).end();
res.status(204).end();
});
});
@@ -696,6 +699,10 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
@@ -696,6 +699,10 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
following: /^(1|true)$/.test(String(req.query.following || '')) ? '1' : '',
following: /^(1|true)$/.test(String(req.query.following || '')) ? '1' : '',
cursor: qCursor(req.query.cursor), limit: qLimit(req.query.limit, 40),
cursor: qCursor(req.query.cursor), limit: qLimit(req.query.limit, 40),
}));
}));
+ router.get('/feed/search', (req, res) => relay(req, res, '/api/v1/feed/search', {
+ q: qWords(req.query.q),
+ }));
+ router.get('/feed/examples', (req, res) => relay(req, res, '/api/v1/feed/examples', {}));
router.get('/wall', (req, res) => relay(req, res, '/api/v1/pulse', {
router.get('/wall', (req, res) => relay(req, res, '/api/v1/pulse', {
limit: qLimit(req.query.limit, 50), after: qCursor(req.query.after), cursor: qCursor(req.query.cursor),
limit: qLimit(req.query.limit, 50), after: qCursor(req.query.after), cursor: qCursor(req.query.cursor),
for: req.query.for === 'me' ? 'me' : '', origin: ['user', 'ai', 'system', 'all'].includes(String(req.query.origin || '')) ? String(req.query.origin) : '',
for: req.query.for === 'me' ? 'me' : '', origin: ['user', 'ai', 'system', 'all'].includes(String(req.query.origin || '')) ? String(req.query.origin) : '',
@@ -16,6 +16,8 @@ const { createFrameService } = require('../server/frame/service');
@@ -16,6 +16,8 @@ const { createFrameService } = require('../server/frame/service');
res.setHeader('content-type', 'application/json');
res.setHeader('content-type', 'application/json');
const u = new URL(req.url, 'http://x');
const u = new URL(req.url, 'http://x');
if (u.pathname === '/api/v1/feed') return res.end(JSON.stringify({ items: [{ id: 'p:1', kind: u.searchParams.get('kind') || 'post', url: 'https://openvibe.community/p/1', title: 'A post' }], next_cursor: 'c2' }));
if (u.pathname === '/api/v1/feed') return res.end(JSON.stringify({ items: [{ id: 'p:1', kind: u.searchParams.get('kind') || 'post', url: 'https://openvibe.community/p/1', title: 'A post' }], next_cursor: 'c2' }));
+ if (u.pathname === '/api/v1/feed/search') return res.end(JSON.stringify({ items: [{ id: 'p:2', title: u.searchParams.get('q') }], next_cursor: null }));
+ if (u.pathname === '/api/v1/feed/examples') return res.end(JSON.stringify({ examples: [{ text: 'New photos', reason: 'kind' }] }));
if (u.pathname === '/api/v1/me/status' && req.method === 'POST') { let body = ''; req.on('data', (c) => { body += c; }); req.on('end', () => { asked.push({ url: req.url, auth: req.headers.authorization || '', body }); res.statusCode = req.headers.authorization ? 201 : 401; res.end(JSON.stringify({ ok: true })); }); return undefined; }
if (u.pathname === '/api/v1/me/status' && req.method === 'POST') { let body = ''; req.on('data', (c) => { body += c; }); req.on('end', () => { asked.push({ url: req.url, auth: req.headers.authorization || '', body }); res.statusCode = req.headers.authorization ? 201 : 401; res.end(JSON.stringify({ ok: true })); }); return undefined; }
if ((u.pathname === '/api/v1/spaces/status/attachments' || u.pathname === '/api/v1/posts') && req.method === 'POST') {
if ((u.pathname === '/api/v1/spaces/status/attachments' || u.pathname === '/api/v1/posts') && req.method === 'POST') {
const chunks = []; req.on('data', (c) => chunks.push(c));
const chunks = []; req.on('data', (c) => chunks.push(c));
@@ -42,9 +44,17 @@ const { createFrameService } = require('../server/frame/service');
@@ -42,9 +44,17 @@ const { createFrameService } = require('../server/frame/service');
assert.strictEqual(d.next_cursor, 'c2');
assert.strictEqual(d.next_cursor, 'c2');
const first = new URL(asked[0].url, 'http://x');
const first = new URL(asked[0].url, 'http://x');
assert.deepStrictEqual([first.pathname, first.searchParams.get('kind'), first.searchParams.get('limit'), first.searchParams.get('cursor')], ['/api/v1/feed', 'clip', '12', 'c1']);
assert.deepStrictEqual([first.pathname, first.searchParams.get('kind'), first.searchParams.get('limit'), first.searchParams.get('cursor')], ['/api/v1/feed', 'clip', '12', 'c1']);
+ ({ r, d } = await get('/feed/search?q=New%20photos'));
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual(d.items[0].title, 'New photos');
+ assert.strictEqual(new URL(asked[asked.length - 1].url, 'http://x').pathname, '/api/v1/feed/search');
+ ({ r, d } = await get('/feed/examples'));
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual(d.examples[0].text, 'New photos');
+ const afterSearch = asked.length;
({ d } = await get('/feed?kind=clip&limit=12&cursor=c1'));
({ d } = await get('/feed?kind=clip&limit=12&cursor=c1'));
- assert.strictEqual(asked.length, 1, 'the same anonymous page within 30 s is answered from memory');
+ assert.strictEqual(asked.length, afterSearch, 'the same anonymous page within 30 s is answered from memory');
({ r, d } = await get('/wall?limit=20&for=me', { Authorization: 'Bearer abcdefghijklmnop.qrstu', Cookie: 'ov_session=secret' }));
({ r, d } = await get('/wall?limit=20&for=me', { Authorization: 'Bearer abcdefghijklmnop.qrstu', Cookie: 'ov_session=secret' }));
const wall = asked[asked.length - 1];
const wall = asked[asked.length - 1];