Skip to content

Commit 30e4c37

Browse files
Disallow MP3 Full Server Cleanse and pin Node runtime
Merge PR #19: return JSON 422 for unsupported MP3 server cleanse, preserve Quick Cleanse for MP3, remove unsupported exiftool.execute path, surface backend detail, and constrain native Node runtime to supported versions.
2 parents bfe4fe0 + c8c1ccc commit 30e4c37

7 files changed

Lines changed: 46 additions & 14 deletions

File tree

.node-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
20.20.2

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ The source code is available at [github.com/ChrisAdamsdevelopment/SpectraCleanse
4747
For a step-by-step manual validation flow (local, API smoke, auth, billing, upload, cleanse, Docker, and production readiness), see [`docs/manual-qa-checklist.md`](docs/manual-qa-checklist.md).
4848

4949
- Browser metadata analysis uses maintained `music-metadata` with graceful fallback (`parseError`) when parsing fails, times out, or is skipped for very large files.
50-
- Quick Cleanse metadata writing remains local/browser-side (MP3 via `browser-id3-writer`), while Full Server Cleanse still runs through `/api/process`.
50+
- Quick Cleanse metadata writing remains local/browser-side (MP3 via `browser-id3-writer`).
51+
- Full Server Cleanse runs through `/api/process` for supported non-MP3 formats; MP3 requests are rejected with HTTP `422` and guidance to use Quick Cleanse Browser.
5152

5253
---
5354

@@ -57,6 +58,16 @@ Questions, partnerships, or enterprise enquiries: [hello@spectracleanse.com](mai
5758

5859
---
5960

61+
62+
## Native Node deployment runtime
63+
64+
- Native Render/Node deployments should use **Node 20.20.2** (recommended) or a Node version within the supported engines range: `>=18 <23`.
65+
- If Render defaults your service to a newer Node release, set `NODE_VERSION=20.20.2` in the service environment.
66+
- Node 24 is currently not supported for native installs in validation because `better-sqlite3` native compilation failed under Node 24.
67+
- Docker deployments already pin Node 18 via the repo Dockerfile.
68+
69+
---
70+
6071
## Docker production deployment
6172

6273
This repository includes a multi-stage `Dockerfile` that builds the frontend and packages `dist/` into the final runtime image so `server.js` can serve the SPA in production.

app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ export default function App() {
654654

655655
if (!res.ok) {
656656
const errBody = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
657-
throw new Error(errBody.error || `Server error ${res.status}`);
657+
throw new Error(errBody.detail || errBody.error || `Server error ${res.status}`);
658658
}
659659

660660
const blob = await res.blob();

docs/manual-qa-checklist.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,17 @@ Run these checks against your local server:
9898

9999
## 9) Full Server Cleanse QA
100100

101-
1. Run server cleanse on a supported file.
101+
1. Run server cleanse on a supported non-MP3 file.
102102
2. Verify `/api/process` returns downloadable file response.
103-
3. Verify usage meter/counter updates.
103+
3. Verify usage meter/counter updates only after successful delivered files.
104104
4. Verify forensic/report information appears when present.
105-
5. Force or simulate `401` from protected endpoint.
105+
5. Upload MP3 to Full Server Cleanse and verify HTTP `422` JSON:
106+
- Expected error: `MP3 server cleanse is not supported`.
107+
- Expected detail tells user to use Quick Cleanse (Browser) for MP3.
108+
- Expected usage counter does **not** increment on this rejection.
109+
6. Force or simulate `401` from protected endpoint.
106110
- Expected: user is logged out/reauth requested.
107-
6. Force or simulate `402`.
111+
7. Force or simulate `402`.
108112
- Expected: upgrade modal opens.
109113

110114
## 10) Object URL / download safety QA

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "SpectraCleanse AI",
55
"main": "server.js",
66
"engines": {
7-
"node": ">=18"
7+
"node": ">=18 <23"
88
},
99
"scripts": {
1010
"start": "node server.js",

server.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,19 @@ app.post('/api/process', requireAuth, upload.single('file'), async (req, res) =>
451451
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
452452

453453
const userId = req.user.sub;
454+
const inputPath = req.file.path;
455+
const originalName = req.file.originalname || '';
456+
const ext = path.extname(originalName).toLowerCase() || '.mp3';
457+
const mime = (req.file.mimetype || '').toLowerCase();
458+
const isMp3 = ext === '.mp3' || mime === 'audio/mpeg';
459+
460+
if (isMp3) {
461+
await fs.remove(inputPath).catch(() => {});
462+
return res.status(422).json({
463+
error: 'MP3 server cleanse is not supported',
464+
detail: 'Use Quick Cleanse (Browser) for MP3 metadata rewriting, or upload MP4/M4A/WAV/FLAC for Full Server Cleanse.',
465+
});
466+
}
454467

455468
// ── Tier-based usage enforcement ─────────────────────────────────────────
456469
// Always re-read plan from DB so upgrades (via webhook) take effect
@@ -474,8 +487,6 @@ app.post('/api/process', requireAuth, upload.single('file'), async (req, res) =>
474487
// ── End enforcement ───────────────────────────────────────────────────────
475488

476489
const { title, description, tags, artist, genre, lyrics, platform = 'General' } = req.body;
477-
const inputPath = req.file.path;
478-
const ext = path.extname(req.file.originalname).toLowerCase() || '.mp3';
479490
const outputPath = path.join('uploads', `out_${Date.now()}${ext}`);
480491

481492
try {
@@ -490,16 +501,21 @@ app.post('/api/process', requireAuth, upload.single('file'), async (req, res) =>
490501
const beforeTags = await exiftool.read(outputPath);
491502
const beforeKeys = new Set(Object.keys(beforeTags));
492503

493-
// Phase 2: Nuclear wipe
504+
// Phase 2: Nuclear wipe (supported exiftool-vendored path only)
494505
try {
495-
await exiftool.execute('-all=', '-XMP:all=', '-IPTC:all=', '-overwrite_original', outputPath);
496-
} catch (wipeErr) {
497-
console.warn('Primary metadata wipe failed, retrying with exiftool.write fallback:', wipeErr.message);
498506
await exiftool.write(
499507
outputPath,
500508
{},
501509
['-all=', '-XMP:all=', '-IPTC:all=', '-overwrite_original']
502510
);
511+
} catch (wipeErr) {
512+
console.warn('Primary metadata wipe failed:', wipeErr.message);
513+
await fs.remove(inputPath).catch(() => {});
514+
await fs.remove(outputPath).catch(() => {});
515+
return res.status(422).json({
516+
error: 'Server cleanse unsupported for this format',
517+
detail: 'This file format cannot be safely metadata-wiped on the server. Use Quick Cleanse (Browser) for MP3 or try MP4/M4A/WAV/FLAC for Full Server Cleanse.',
518+
});
503519
}
504520

505521
// Phase 3: Platform-aware SEO injection

0 commit comments

Comments
 (0)