openvibers/openvibe.media Commits
Paste cutover switches: PASTES_FROZEN_APPS answers 410 to paste writes for apps that moved to Community; PASTES_MOVED_TO 301s /p/:slug and its raw text there; screenshot bytes stay served here
Changes
@@@ -44,6 +44,15 @@ const { tenantAuth, tenantCors } = require('../auth');const router = express.Router({ mergeParams: true });router.use(tenantCors);++// Apps whose pastes moved to OpenVibe.Community (PASTES_FROZEN_APPS, roadmap Wave 5) get no more writes++// here; reads keep working for anything still pointed at Media during the switch.++router.use((req, res, next) => {++ const frozen = String(process.env.PASTES_FROZEN_APPS || '').split(',').map(s => s.trim()).filter(Boolean);++ if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS' && frozen.includes(String(req.params.app))) {++ return res.status(410).json({ error: 'Pastes for this app moved to OpenVibe.Community', code: 'pastes.moved' });++ }++ next();++});// ── Screenshot upload storage ───────────────────────────────const SCREENSHOTS_DIR = path.join(config.pastes.path, 'screenshots');@@@ -434,7 +434,12 @@ router.get('/f/:key', (req, res) => {// ── Pastes ───────────────────────────────────────────────────// The page itself is rendered in pages.js (thin viewer; canonical on Community).++// Pastes moved to OpenVibe.Community (roadmap Wave 5, PASTES_MOVED_TO): the page and its text are++// Community's now, so send people there. Screenshot bytes keep being served from here.++const movedTo = () => String(process.env.PASTES_MOVED_TO || '').replace(/\/$/, '');++router.get('/p/:slug', optionalIdentity, (req, res) => {++ if (movedTo()) return res.redirect(301, `${movedTo()}/p/${encodeURIComponent(req.params.slug)}`);try {const paste = db.getPasteBySlug(String(req.params.slug));if (!paste) return res.status(404).send('Paste not found');@@@ -478,6 +483,7 @@ router.get('/p/:slug/raw', (req, res) => {return res.redirect(302, `/p/${encodeURIComponent(paste.slug)}/screenshot`);}if (!paste || paste.type !== 'paste') return res.status(404).send('Not found');++ if (movedTo()) return res.redirect(301, `${movedTo()}/p/${encodeURIComponent(paste.slug)}/raw`);if (paste.visibility === 'private') return res.status(404).send('Not found');// Burn after readnnew file mode 100644@@@ -0,0 +1,56 @@++'use strict';++// After the Wave 5 cutover: paste writes for a frozen app answer 410, the paste page and its text 301 to++// OpenVibe.Community, and screenshot bytes are still served from here. Unset, nothing changes.++const assert = require('assert');++const fs = require('fs');++const os = require('os');++const path = require('path');++const http = require('http');++const express = require('express');++++const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-media-moved-'));++process.env.DB_PATH = path.join(tmp, 'media.db');++const db = require('../server/db/database');++require('../server/views/service').ensureSchema();++db.upsertApp({ app_id: 'live', api_key: 'live-key' });++const shot = path.join(tmp, 'shot.png');++fs.writeFileSync(shot, Buffer.from('\x89PNG\r\n\x1a\n'));++db.getDb().prepare("INSERT INTO pastes (app_id, slug, type, title, content, visibility, screenshot_path) VALUES ('live', 'text-1', 'paste', 't', 'hello', 'public', NULL), ('live', 'shot-1', 'screenshot', 's', '', 'public', ?)").run(shot);++++const app = express();++app.use(express.json());++app.use('/api/v1/:app/pastes', require('../server/pastes/routes'));++app.use(require('../server/public/routes'));++const server = http.createServer(app);++++(async () => {++ await new Promise((r) => server.listen(0, '127.0.0.1', r));++ const base = `http://127.0.0.1:${server.address().port}`;++ const get = (p) => fetch(base + p, { redirect: 'manual' });++ const post = () => fetch(`${base}/api/v1/live/pastes`, { method: 'POST', headers: { authorization: 'Bearer live-key', 'content-type': 'application/json' }, body: JSON.stringify({ content: 'x' }) });++++ // Before the switch: business as usual.++ assert.strictEqual((await get('/p/text-1')).status, 200);++ assert.strictEqual(await (await get('/p/text-1/raw')).text(), 'hello');++ assert.strictEqual((await post()).status, 201);++++ process.env.PASTES_MOVED_TO = 'https://openvibe.community/';++ process.env.PASTES_FROZEN_APPS = 'live';++ let r = await post();++ assert.strictEqual(r.status, 410); assert.strictEqual((await r.json()).code, 'pastes.moved');++ r = await fetch(`${base}/api/v1/live/pastes/text-1`, { headers: { authorization: 'Bearer live-key' } });++ assert.strictEqual(r.status, 200, 'reads still work');++ r = await get('/p/text-1');++ assert.strictEqual(r.status, 301); assert.strictEqual(r.headers.get('location'), 'https://openvibe.community/p/text-1');++ r = await get('/p/text-1/raw');++ assert.strictEqual(r.status, 301); assert.strictEqual(r.headers.get('location'), 'https://openvibe.community/p/text-1/raw');++ r = await get('/p/shot-1/raw');++ assert.strictEqual(r.status, 302); assert.strictEqual(r.headers.get('location'), '/p/shot-1/screenshot', 'image pastes keep their local bytes');++ r = await get('/p/shot-1/screenshot');++ assert.strictEqual(r.status, 200, 'screenshot bytes are still served here');++ assert.ok(db.getPasteBySlug('text-1'), 'no read deletes anything after the switch');++++ server.close();++ fs.rmSync(tmp, { recursive: true, force: true });++ console.log('pastes moved: all checks passed');++})().catch((err) => { console.error(err); process.exit(1); });
@@ -44,6 +44,15 @@ const { tenantAuth, tenantCors } = require('../auth');
@@ -44,6 +44,15 @@ const { tenantAuth, tenantCors } = require('../auth');
const router = express.Router({ mergeParams: true });
const router = express.Router({ mergeParams: true });
router.use(tenantCors);
router.use(tenantCors);
+// Apps whose pastes moved to OpenVibe.Community (PASTES_FROZEN_APPS, roadmap Wave 5) get no more writes
+// here; reads keep working for anything still pointed at Media during the switch.
+router.use((req, res, next) => {
+ const frozen = String(process.env.PASTES_FROZEN_APPS || '').split(',').map(s => s.trim()).filter(Boolean);
+ if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS' && frozen.includes(String(req.params.app))) {
+ return res.status(410).json({ error: 'Pastes for this app moved to OpenVibe.Community', code: 'pastes.moved' });
+ }
+ next();
+});
// ── Screenshot upload storage ───────────────────────────────
// ── Screenshot upload storage ───────────────────────────────
const SCREENSHOTS_DIR = path.join(config.pastes.path, 'screenshots');
const SCREENSHOTS_DIR = path.join(config.pastes.path, 'screenshots');
@@ -434,7 +434,12 @@ router.get('/f/:key', (req, res) => {
@@ -434,7 +434,12 @@ router.get('/f/:key', (req, res) => {
// ── Pastes ───────────────────────────────────────────────────
// ── Pastes ───────────────────────────────────────────────────
// The page itself is rendered in pages.js (thin viewer; canonical on Community).
// The page itself is rendered in pages.js (thin viewer; canonical on Community).
+// Pastes moved to OpenVibe.Community (roadmap Wave 5, PASTES_MOVED_TO): the page and its text are
+// Community's now, so send people there. Screenshot bytes keep being served from here.
+const movedTo = () => String(process.env.PASTES_MOVED_TO || '').replace(/\/$/, '');
+
router.get('/p/:slug', optionalIdentity, (req, res) => {
router.get('/p/:slug', optionalIdentity, (req, res) => {
+ if (movedTo()) return res.redirect(301, `${movedTo()}/p/${encodeURIComponent(req.params.slug)}`);
try {
try {
const paste = db.getPasteBySlug(String(req.params.slug));
const paste = db.getPasteBySlug(String(req.params.slug));
if (!paste) return res.status(404).send('Paste not found');
if (!paste) return res.status(404).send('Paste not found');
@@ -478,6 +483,7 @@ router.get('/p/:slug/raw', (req, res) => {
@@ -478,6 +483,7 @@ router.get('/p/:slug/raw', (req, res) => {
return res.redirect(302, `/p/${encodeURIComponent(paste.slug)}/screenshot`);
return res.redirect(302, `/p/${encodeURIComponent(paste.slug)}/screenshot`);
}
}
if (!paste || paste.type !== 'paste') return res.status(404).send('Not found');
if (!paste || paste.type !== 'paste') return res.status(404).send('Not found');
+ if (movedTo()) return res.redirect(301, `${movedTo()}/p/${encodeURIComponent(paste.slug)}/raw`);
if (paste.visibility === 'private') return res.status(404).send('Not found');
if (paste.visibility === 'private') return res.status(404).send('Not found');
// Burn after read
// Burn after read
new file mode 100644
new file mode 100644
@@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
+'use strict';
+// After the Wave 5 cutover: paste writes for a frozen app answer 410, the paste page and its text 301 to
+// OpenVibe.Community, and screenshot bytes are still served from here. Unset, nothing changes.
+const assert = require('assert');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const http = require('http');
+const express = require('express');
+
+const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-media-moved-'));
+process.env.DB_PATH = path.join(tmp, 'media.db');
+const db = require('../server/db/database');
+require('../server/views/service').ensureSchema();
+db.upsertApp({ app_id: 'live', api_key: 'live-key' });
+const shot = path.join(tmp, 'shot.png');
+fs.writeFileSync(shot, Buffer.from('\x89PNG\r\n\x1a\n'));
+db.getDb().prepare("INSERT INTO pastes (app_id, slug, type, title, content, visibility, screenshot_path) VALUES ('live', 'text-1', 'paste', 't', 'hello', 'public', NULL), ('live', 'shot-1', 'screenshot', 's', '', 'public', ?)").run(shot);
+
+const app = express();
+app.use(express.json());
+app.use('/api/v1/:app/pastes', require('../server/pastes/routes'));
+app.use(require('../server/public/routes'));
+const server = http.createServer(app);
+
+(async () => {
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
+ const base = `http://127.0.0.1:${server.address().port}`;
+ const get = (p) => fetch(base + p, { redirect: 'manual' });
+ const post = () => fetch(`${base}/api/v1/live/pastes`, { method: 'POST', headers: { authorization: 'Bearer live-key', 'content-type': 'application/json' }, body: JSON.stringify({ content: 'x' }) });
+
+ // Before the switch: business as usual.
+ assert.strictEqual((await get('/p/text-1')).status, 200);
+ assert.strictEqual(await (await get('/p/text-1/raw')).text(), 'hello');
+ assert.strictEqual((await post()).status, 201);
+
+ process.env.PASTES_MOVED_TO = 'https://openvibe.community/';
+ process.env.PASTES_FROZEN_APPS = 'live';
+ let r = await post();
+ assert.strictEqual(r.status, 410); assert.strictEqual((await r.json()).code, 'pastes.moved');
+ r = await fetch(`${base}/api/v1/live/pastes/text-1`, { headers: { authorization: 'Bearer live-key' } });
+ assert.strictEqual(r.status, 200, 'reads still work');
+ r = await get('/p/text-1');
+ assert.strictEqual(r.status, 301); assert.strictEqual(r.headers.get('location'), 'https://openvibe.community/p/text-1');
+ r = await get('/p/text-1/raw');
+ assert.strictEqual(r.status, 301); assert.strictEqual(r.headers.get('location'), 'https://openvibe.community/p/text-1/raw');
+ r = await get('/p/shot-1/raw');
+ assert.strictEqual(r.status, 302); assert.strictEqual(r.headers.get('location'), '/p/shot-1/screenshot', 'image pastes keep their local bytes');
+ r = await get('/p/shot-1/screenshot');
+ assert.strictEqual(r.status, 200, 'screenshot bytes are still served here');
+ assert.ok(db.getPasteBySlug('text-1'), 'no read deletes anything after the switch');
+
+ server.close();
+ fs.rmSync(tmp, { recursive: true, force: true });
+ console.log('pastes moved: all checks passed');
+})().catch((err) => { console.error(err); process.exit(1); });