openvibers/openvibe.media Commits

GET /clips: before= and unvisited=1 (find stale auto-clips no person opened) (#292)

OP OpenVibers committed 11 days ago · c3f8738e1a · 3 files, +21 −1

Live's audit (Live#443) counts a clip as watched only when content_visits has a visitor for it: the play
counter moves for crawlers, so 2 of Live's old public auto-clips have 0 plays but 29 were never opened by a
person. before= bounds the list to clips cut before a time; unvisited=1 keeps those with no visitor row, and
only the owning app may ask it (a user's call ignores it). Both count in total.

Changes

ddiff --git a/server/db/database.js b/server/db/database.js
iindex 29c0255..ed3bdae 100644
---- a/server/db/database.js
++++ b/server/db/database.js
@@@ -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, ch
if (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); }
ddiff --git a/server/vod/clips-routes.js b/server/vod/clips-routes.js
iindex 653fe3f..3d3723a 100644
---- a/server/vod/clips-routes.js
++++ b/server/vod/clips-routes.js
@@@ -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);
ddiff --git a/test/clip-origin-filters.test.js b/test/clip-origin-filters.test.js
iindex 207a00b..55c0dd5 100644
---- a/test/clip-origin-filters.test.js
++++ b/test/clip-origin-filters.test.js
@@@ -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));