openvibers/openvibe.media Commits
Media: the frozen paste tables are dropped (contract after 0001); getAppStats carries no paste keys (#89)
Changes
nnew file mode 100644@@@ -0,0 +1,16 @@++-- phase: contract++-- after: 0001++-- OpenVibe.Media: the frozen paste tables go. Pastes moved to OpenVibe.Community on 2026-09-22; Media#75++-- (2026-10-10 13:38 UTC) stopped every read and write of `pastes`, `paste_likes` and `paste_comments`++-- (test/paste-tables-unread.test.js), and production showed no scan or write of them after it. They were created by++-- 0001, so the N-1 window has long passed. The legacy screenshot and avatar objects stay: they are media_objects++-- found by legacy_ref (/p/:slug/screenshot 301s to /o/<id>), and DROP TABLE fires no row trigger, so dropping++-- pastes marks no object deleted. A pg_dump of the three tables was kept before this ran++-- (/root/ov_media-pastes-pre-drop-2026-10-11.dump on the host).++DROP TABLE IF EXISTS paste_comments;++DROP TABLE IF EXISTS paste_likes;++DROP TABLE IF EXISTS pastes;++DROP FUNCTION IF EXISTS trg_pastes_hold_guard_v2();++DROP FUNCTION IF EXISTS trg_pastes_object_deleted();++DELETE FROM media_settings WHERE key IN ('paste_max_size_kb', 'paste_screenshot_max_size_mb', 'paste_cooldown_seconds',++ 'paste_max_per_user_per_day', 'paste_comment_cooldown_seconds', 'paste_comment_max_length', 'paste_comment_anon_allowed');@@@ -332,10 +332,6 @@ async function getAppStats(appId) {return {vods: await c(`SELECT COUNT(*) n ${vodBase}`, [appId]),clips: await c(`SELECT COUNT(*) n ${clipBase}`, [appId]),-- // Pastes belong to Community, and Live reads them there; null keys remain for one release for N-1 clients.-- pastes: null,-- pasteImages: null,-- pasteText: null,durationSeconds: await c(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase}`, [appId]),recent: {vods: await win(`SELECT COUNT(*) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]),nnew file mode 100644@@@ -0,0 +1,48 @@++'use strict';++// Migration 0007 drops the frozen paste tables (pastes moved to OpenVibe.Community; Media#75 stopped every read and++// write). On a database that already holds them, it waits out its N-1 window after 0001 and then drops the three++// tables, their trigger functions and the paste settings — and leaves the legacy screenshot objects alone: DROP TABLE++// fires no row trigger, so trg_pastes_object_deleted never marks an object deleted.++const assert = require('assert');++const fs = require('fs');++const os = require('os');++const path = require('path');++const { createDb } = require('openvibe-sdk/db');++++const MIGRATIONS = path.join(__dirname, '..', 'migrations');++const quiet = { log() {}, warn() {}, error() {} };++++(async () => {++ // The schema as production had it before this release: every migration up to 0006.++ const before = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-media-0006-'));++ for (const f of fs.readdirSync(MIGRATIONS)) if (/^000[1-6]_/.test(f)) fs.copyFileSync(path.join(MIGRATIONS, f), path.join(before, f));++ const db = createDb({ pglite: true, service: 'paste-drop-test' });++ await db.migrate({ dir: before, log: quiet });++++ await db.exec(`INSERT INTO media_objects (id, app_id, namespace, kind, visibility, lifecycle_status, legacy_ref)++ VALUES ('med_01JZ0000000000000000000001', 'live', 'live', 'screenshot', 'public', 'ready', 'legacy:live:paste:old-shot')`);++ await db.exec(`INSERT INTO pastes (slug, type, title, object_id) VALUES ('old-shot', 'screenshot', 'An old screenshot', 'med_01JZ0000000000000000000001')`);++ await db.exec(`INSERT INTO paste_likes (paste_id, user_id) SELECT id, 7 FROM pastes WHERE slug = 'old-shot'`);++++ // Within the window after 0001 the drop waits, and nothing after it runs.++ const held = await db.migrate({ dir: MIGRATIONS, log: quiet });++ assert.deepStrictEqual(held.held.map((h) => h.id), ['0007'], 'the contract waits for its N-1 window');++ assert.ok(await db.value("SELECT to_regclass('pastes')::text"), 'the tables are still there while it waits');++++ // A week later it runs.++ const later = await db.migrate({ dir: MIGRATIONS, now: () => Date.now() + 8 * 86400000, log: quiet });++ assert.deepStrictEqual(later.applied.map((a) => a.id), ['0007']);++ for (const t of ['pastes', 'paste_likes', 'paste_comments']) {++ assert.strictEqual(await db.value(`SELECT to_regclass('${t}')::text`), null, `${t} is dropped`);++ }++ const fns = await db.many("SELECT proname FROM pg_proc WHERE proname IN ('trg_pastes_hold_guard_v2', 'trg_pastes_object_deleted')");++ assert.deepStrictEqual(fns, [], 'their trigger functions go too');++ const settings = await db.many("SELECT key FROM media_settings WHERE key LIKE 'paste%'");++ assert.deepStrictEqual(settings, [], 'and the paste settings');++ const obj = await db.maybe("SELECT lifecycle_status FROM media_objects WHERE id = 'med_01JZ0000000000000000000001'");++ assert.strictEqual(obj.lifecycle_status, 'ready', 'the legacy screenshot object is untouched (no row trigger fired)');++++ await db.close();++ fs.rmSync(before, { recursive: true, force: true });++ console.log('paste tables dropped: all checks passed');++})().catch((err) => { console.error(err); process.exit(1); });
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+-- phase: contract
+-- after: 0001
+-- OpenVibe.Media: the frozen paste tables go. Pastes moved to OpenVibe.Community on 2026-09-22; Media#75
+-- (2026-10-10 13:38 UTC) stopped every read and write of `pastes`, `paste_likes` and `paste_comments`
+-- (test/paste-tables-unread.test.js), and production showed no scan or write of them after it. They were created by
+-- 0001, so the N-1 window has long passed. The legacy screenshot and avatar objects stay: they are media_objects
+-- found by legacy_ref (/p/:slug/screenshot 301s to /o/<id>), and DROP TABLE fires no row trigger, so dropping
+-- pastes marks no object deleted. A pg_dump of the three tables was kept before this ran
+-- (/root/ov_media-pastes-pre-drop-2026-10-11.dump on the host).
+DROP TABLE IF EXISTS paste_comments;
+DROP TABLE IF EXISTS paste_likes;
+DROP TABLE IF EXISTS pastes;
+DROP FUNCTION IF EXISTS trg_pastes_hold_guard_v2();
+DROP FUNCTION IF EXISTS trg_pastes_object_deleted();
+DELETE FROM media_settings WHERE key IN ('paste_max_size_kb', 'paste_screenshot_max_size_mb', 'paste_cooldown_seconds',
+ 'paste_max_per_user_per_day', 'paste_comment_cooldown_seconds', 'paste_comment_max_length', 'paste_comment_anon_allowed');
@@ -332,10 +332,6 @@ async function getAppStats(appId) {
@@ -332,10 +332,6 @@ async function getAppStats(appId) {
return {
return {
vods: await c(`SELECT COUNT(*) n ${vodBase}`, [appId]),
vods: await c(`SELECT COUNT(*) n ${vodBase}`, [appId]),
clips: await c(`SELECT COUNT(*) n ${clipBase}`, [appId]),
clips: await c(`SELECT COUNT(*) n ${clipBase}`, [appId]),
- // Pastes belong to Community, and Live reads them there; null keys remain for one release for N-1 clients.
- pastes: null,
- pasteImages: null,
- pasteText: null,
durationSeconds: await c(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase}`, [appId]),
durationSeconds: await c(`SELECT COALESCE(SUM(duration_seconds),0) n ${vodBase}`, [appId]),
recent: {
recent: {
vods: await win(`SELECT COUNT(*) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]),
vods: await win(`SELECT COUNT(*) n ${vodBase} AND created_at >= datetime('now', ?)`, [appId]),
new file mode 100644
new file mode 100644
@@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
+'use strict';
+// Migration 0007 drops the frozen paste tables (pastes moved to OpenVibe.Community; Media#75 stopped every read and
+// write). On a database that already holds them, it waits out its N-1 window after 0001 and then drops the three
+// tables, their trigger functions and the paste settings — and leaves the legacy screenshot objects alone: DROP TABLE
+// fires no row trigger, so trg_pastes_object_deleted never marks an object deleted.
+const assert = require('assert');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const { createDb } = require('openvibe-sdk/db');
+
+const MIGRATIONS = path.join(__dirname, '..', 'migrations');
+const quiet = { log() {}, warn() {}, error() {} };
+
+(async () => {
+ // The schema as production had it before this release: every migration up to 0006.
+ const before = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-media-0006-'));
+ for (const f of fs.readdirSync(MIGRATIONS)) if (/^000[1-6]_/.test(f)) fs.copyFileSync(path.join(MIGRATIONS, f), path.join(before, f));
+ const db = createDb({ pglite: true, service: 'paste-drop-test' });
+ await db.migrate({ dir: before, log: quiet });
+
+ await db.exec(`INSERT INTO media_objects (id, app_id, namespace, kind, visibility, lifecycle_status, legacy_ref)
+ VALUES ('med_01JZ0000000000000000000001', 'live', 'live', 'screenshot', 'public', 'ready', 'legacy:live:paste:old-shot')`);
+ await db.exec(`INSERT INTO pastes (slug, type, title, object_id) VALUES ('old-shot', 'screenshot', 'An old screenshot', 'med_01JZ0000000000000000000001')`);
+ await db.exec(`INSERT INTO paste_likes (paste_id, user_id) SELECT id, 7 FROM pastes WHERE slug = 'old-shot'`);
+
+ // Within the window after 0001 the drop waits, and nothing after it runs.
+ const held = await db.migrate({ dir: MIGRATIONS, log: quiet });
+ assert.deepStrictEqual(held.held.map((h) => h.id), ['0007'], 'the contract waits for its N-1 window');
+ assert.ok(await db.value("SELECT to_regclass('pastes')::text"), 'the tables are still there while it waits');
+
+ // A week later it runs.
+ const later = await db.migrate({ dir: MIGRATIONS, now: () => Date.now() + 8 * 86400000, log: quiet });
+ assert.deepStrictEqual(later.applied.map((a) => a.id), ['0007']);
+ for (const t of ['pastes', 'paste_likes', 'paste_comments']) {
+ assert.strictEqual(await db.value(`SELECT to_regclass('${t}')::text`), null, `${t} is dropped`);
+ }
+ const fns = await db.many("SELECT proname FROM pg_proc WHERE proname IN ('trg_pastes_hold_guard_v2', 'trg_pastes_object_deleted')");
+ assert.deepStrictEqual(fns, [], 'their trigger functions go too');
+ const settings = await db.many("SELECT key FROM media_settings WHERE key LIKE 'paste%'");
+ assert.deepStrictEqual(settings, [], 'and the paste settings');
+ const obj = await db.maybe("SELECT lifecycle_status FROM media_objects WHERE id = 'med_01JZ0000000000000000000001'");
+ assert.strictEqual(obj.lifecycle_status, 'ready', 'the legacy screenshot object is untouched (no row trigger fired)');
+
+ await db.close();
+ fs.rmSync(before, { recursive: true, force: true });
+ console.log('paste tables dropped: all checks passed');
+})().catch((err) => { console.error(err); process.exit(1); });