openvibers/openvibe.media Commits
GET /clips: before= and unvisited=1 (find stale auto-clips no person opened) (#292)
Changes
@@@ -471,7 +471,7 @@ async function getClipById(id, appId = null) {return await get(`SELECT * FROM clips WHERE id = ?${clause}`, params);}--function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, channel_user_id = null, include_private = false, hide_self = false, auto_generated = null, ready_only = false, since = null } = {}) {++function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, channel_user_id = null, include_private = false, hide_self = false, auto_generated = null, ready_only = false, since = null, before = null, unvisited = false } = {}) {const conds = ['app_id = ?'];const params = [appId];if (!include_private) conds.push('is_public = 1');@@@ -481,6 +481,12 @@ function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, chif (ready_only) conds.push("COALESCE(status, 'ready') = 'ready'");const after = sinceParam(since);if (after) { conds.push('datetime(created_at) >= datetime(?)'); params.push(after); }++ // Cut before a time (Live's stale auto-clip sweep: "older than 30 days").++ const until = sinceParam(before);++ if (until) { conds.push('datetime(created_at) < datetime(?)'); params.push(until); }++ // No person has ever visited it: content_visits counts visitors (never names one), so a play counter that crawlers++ // move is not a person. The route lets only the owning app ask.++ if (unvisited) conds.push("NOT EXISTS (SELECT 1 FROM content_visits v WHERE v.content_type = 'clip' AND v.content_id = clips.id)");if (vod_id != null) { conds.push('vod_id = ?'); params.push(vod_id); }if (stream_id != null) { conds.push('stream_id = ?'); params.push(stream_id); }if (user_id != null) { conds.push('user_id = ?'); params.push(user_id); }@@@ -431,6 +431,9 @@ router.get('/', tenantAuth({ verb: 'list', allowUser: true }), async (req, res)ready_only: String(req.query.status || '') === 'ready'|| (String(req.query.status || '') !== 'any' && !(req.authType === 'app' && ['1', 'true'].includes(String(req.query.include_private || '')))),since: req.query.since || null,++ before: req.query.before || null,++ // Whether any person ever opened a clip is the owning app's to ask (Live hides its stale auto-clips).++ unvisited: req.authType === 'app' && ['1', 'true'].includes(String(req.query.unvisited || '')),};const clips = await db.listClips(req.appId, filters);const total = await db.countClips(req.appId, filters);@@@ -108,6 +108,17 @@ const http = require('http');assert.strictEqual(junk.json.total, 3, 'an unreadable since is ignored, not a 500');});++ await check('before= and unvisited=1 (the owning app only) find the stale clips no person ever opened', async () => {++ const month = new Date(Date.now() - 30 * 86400_000).toISOString();++ assert.deepStrictEqual(ids(await call('GET', `/clips?before=${encodeURIComponent(month)}`)), [14], 'cut before 30 days ago');++ await raw.prepare(`INSERT INTO content_visits (content_type, content_id, visitor) VALUES ('clip', 10, 'v1'), ('clip', 15, 'v2')`).run();++ assert.deepStrictEqual(ids(await call('GET', '/clips?unvisited=1')), [11, 14], 'no visitor row: 11 and 14 (10 and 15 were opened)');++ assert.strictEqual((await call('GET', '/clips?unvisited=1')).json.total, 2, 'the total counts the same filter');++ assert.deepStrictEqual(ids(await call('GET', `/clips?unvisited=1&before=${encodeURIComponent(month)}`)), [14]);++ assert.deepStrictEqual(ids(await call('GET', '/clips?unvisited=1', { user: 5 })), [10, 11, 14, 15], 'a user\'s call: ignored');++ assert.strictEqual((await call('GET', '/clips?before=not-a-date')).json.total, 4, 'an unreadable before is ignored');++ });++await check('POST /clips keeps auto_generated and the description for the app itself', async () => {const r = await call('POST', '/clips', { body: { vod_id: 1, start_s: 10, end_s: 30, title: 'AI pick', auto_generated: true, description: 'A <b>big</b> moment' } });assert.strictEqual(r.status, 202, JSON.stringify(r.json));
@@ -471,7 +471,7 @@ async function getClipById(id, appId = null) {
@@ -471,7 +471,7 @@ async function getClipById(id, appId = null) {
return await get(`SELECT * FROM clips WHERE id = ?${clause}`, params);
return await get(`SELECT * FROM clips WHERE id = ?${clause}`, params);
}
}
-function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, channel_user_id = null, include_private = false, hide_self = false, auto_generated = null, ready_only = false, since = null } = {}) {
+function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, channel_user_id = null, include_private = false, hide_self = false, auto_generated = null, ready_only = false, since = null, before = null, unvisited = false } = {}) {
const conds = ['app_id = ?'];
const conds = ['app_id = ?'];
const params = [appId];
const params = [appId];
if (!include_private) conds.push('is_public = 1');
if (!include_private) conds.push('is_public = 1');
@@ -481,6 +481,12 @@ function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, ch
@@ -481,6 +481,12 @@ function _clipConds(appId, { vod_id = null, stream_id = null, user_id = null, ch
if (ready_only) conds.push("COALESCE(status, 'ready') = 'ready'");
if (ready_only) conds.push("COALESCE(status, 'ready') = 'ready'");
const after = sinceParam(since);
const after = sinceParam(since);
if (after) { conds.push('datetime(created_at) >= datetime(?)'); params.push(after); }
if (after) { conds.push('datetime(created_at) >= datetime(?)'); params.push(after); }
+ // Cut before a time (Live's stale auto-clip sweep: "older than 30 days").
+ const until = sinceParam(before);
+ if (until) { conds.push('datetime(created_at) < datetime(?)'); params.push(until); }
+ // No person has ever visited it: content_visits counts visitors (never names one), so a play counter that crawlers
+ // move is not a person. The route lets only the owning app ask.
+ if (unvisited) conds.push("NOT EXISTS (SELECT 1 FROM content_visits v WHERE v.content_type = 'clip' AND v.content_id = clips.id)");
if (vod_id != null) { conds.push('vod_id = ?'); params.push(vod_id); }
if (vod_id != null) { conds.push('vod_id = ?'); params.push(vod_id); }
if (stream_id != null) { conds.push('stream_id = ?'); params.push(stream_id); }
if (stream_id != null) { conds.push('stream_id = ?'); params.push(stream_id); }
if (user_id != null) { conds.push('user_id = ?'); params.push(user_id); }
if (user_id != null) { conds.push('user_id = ?'); params.push(user_id); }
@@ -431,6 +431,9 @@ router.get('/', tenantAuth({ verb: 'list', allowUser: true }), async (req, res)
@@ -431,6 +431,9 @@ router.get('/', tenantAuth({ verb: 'list', allowUser: true }), async (req, res)
ready_only: String(req.query.status || '') === 'ready'
ready_only: String(req.query.status || '') === 'ready'
|| (String(req.query.status || '') !== 'any' && !(req.authType === 'app' && ['1', 'true'].includes(String(req.query.include_private || '')))),
|| (String(req.query.status || '') !== 'any' && !(req.authType === 'app' && ['1', 'true'].includes(String(req.query.include_private || '')))),
since: req.query.since || null,
since: req.query.since || null,
+ before: req.query.before || null,
+ // Whether any person ever opened a clip is the owning app's to ask (Live hides its stale auto-clips).
+ unvisited: req.authType === 'app' && ['1', 'true'].includes(String(req.query.unvisited || '')),
};
};
const clips = await db.listClips(req.appId, filters);
const clips = await db.listClips(req.appId, filters);
const total = await db.countClips(req.appId, filters);
const total = await db.countClips(req.appId, filters);
@@ -108,6 +108,17 @@ const http = require('http');
@@ -108,6 +108,17 @@ const http = require('http');
assert.strictEqual(junk.json.total, 3, 'an unreadable since is ignored, not a 500');
assert.strictEqual(junk.json.total, 3, 'an unreadable since is ignored, not a 500');
});
});
+ await check('before= and unvisited=1 (the owning app only) find the stale clips no person ever opened', async () => {
+ const month = new Date(Date.now() - 30 * 86400_000).toISOString();
+ assert.deepStrictEqual(ids(await call('GET', `/clips?before=${encodeURIComponent(month)}`)), [14], 'cut before 30 days ago');
+ await raw.prepare(`INSERT INTO content_visits (content_type, content_id, visitor) VALUES ('clip', 10, 'v1'), ('clip', 15, 'v2')`).run();
+ assert.deepStrictEqual(ids(await call('GET', '/clips?unvisited=1')), [11, 14], 'no visitor row: 11 and 14 (10 and 15 were opened)');
+ assert.strictEqual((await call('GET', '/clips?unvisited=1')).json.total, 2, 'the total counts the same filter');
+ assert.deepStrictEqual(ids(await call('GET', `/clips?unvisited=1&before=${encodeURIComponent(month)}`)), [14]);
+ assert.deepStrictEqual(ids(await call('GET', '/clips?unvisited=1', { user: 5 })), [10, 11, 14, 15], 'a user\'s call: ignored');
+ assert.strictEqual((await call('GET', '/clips?before=not-a-date')).json.total, 4, 'an unreadable before is ignored');
+ });
+
await check('POST /clips keeps auto_generated and the description for the app itself', async () => {
await check('POST /clips keeps auto_generated and the description for the app itself', async () => {
const r = await call('POST', '/clips', { body: { vod_id: 1, start_s: 10, end_s: 30, title: 'AI pick', auto_generated: true, description: 'A <b>big</b> moment' } });
const r = await call('POST', '/clips', { body: { vod_id: 1, start_s: 10, end_s: 30, title: 'AI pick', auto_generated: true, description: 'A <b>big</b> moment' } });
assert.strictEqual(r.status, 202, JSON.stringify(r.json));
assert.strictEqual(r.status, 202, JSON.stringify(r.json));