openvibers/openvibe.live Commits

Censor: re-wrap multipart upstream (multer consumes body); export _formData

OP OpenVibers committed 17 Aug 2026 · 2630cff7a2 · 2 files, +19 −2


Changes

ddiff --git a/server/media-client.js b/server/media-client.js
iindex 70e7e7b0..1739c38d 100644
---- a/server/media-client.js
++++ b/server/media-client.js
@@@ -324,7 +324,7 @@ function userTokenFrom(req) {
module.exports = {
MEDIA_URL, MEDIA_PUBLIC_URL, MEDIA_APP_ID,
MediaApiError,
-- request, proxy, userTokenFrom,
++ request, proxy, userTokenFrom, _formData,
// vods
createVod, ingestRtmp, ingestRtpStart, ingestRtpStop,
uploadVodChunk, completeVodChunks, finalizeVod,
ddiff --git a/server/media-proxy/pastes.js b/server/media-proxy/pastes.js
iindex cf7bb06c..542ba5d0 100644
---- a/server/media-proxy/pastes.js
++++ b/server/media-proxy/pastes.js
@@@ -135,7 +135,24 @@ router.put('/:slug', requireAuth, forward(slugPath()));
router.delete('/:slug', requireAuth, forward(slugPath()));
// TODO(contract): paste admin tools (censor, admin/stats, admin/forks, bulk)
// have no Media API v1 endpoints yet — they 404 until Media grows them.
--router.post('/:slug/censor', requireAdmin, shotUpload.single('screenshot'), forwardAsApp(slugPath('/censor')));
++// Censor replaces the screenshot: multer consumed the multipart body, so re-wrap
++// it as a fresh multipart upstream request (app-key auth — Media censor is admin-only).
++router.post('/:slug/censor', requireAdmin, shotUpload.single('screenshot'), async (req, res) => {
++ try {
++ if (!req.file) return res.status(400).json({ error: 'No replacement screenshot uploaded' });
++ const fd = media._formData({}, {
++ buffer: req.file.buffer,
++ filename: req.file.originalname || 'censored.png',
++ contentType: req.file.mimetype || 'image/png',
++ }, 'screenshot');
++ const out = await media.request('POST', `/pastes/${encodeURIComponent(req.params.slug)}/censor`, { body: fd, timeoutMs: 60000 });
++ res.json(out);
++ } catch (err) {
++ if (err && err.name === 'MediaApiError' && err.status) return res.status(err.status).json(err.body || { error: err.message });
++ console.warn('[Pastes proxy] censor:', err.message);
++ res.status(502).json({ error: 'Media service unavailable' });
++ }
++});
router.post('/:slug/fork', forward(slugPath('/fork')));
// Raw content is public on Media (/p/:slug/raw) — bounce the API-shaped URL there.
router.get('/:slug/raw', (req, res) => res.redirect(302, media.pasteRawUrl(req.params.slug)));