Skip to content

Align cleanse mode recommendations with actual server format support#27

Merged
ChrisAdamsdevelopment merged 1 commit into
mainfrom
codex/fix-cleanse-mode-contradictions-and-account-handling
May 18, 2026
Merged

Align cleanse mode recommendations with actual server format support#27
ChrisAdamsdevelopment merged 1 commit into
mainfrom
codex/fix-cleanse-mode-contradictions-and-account-handling

Conversation

@ChrisAdamsdevelopment
Copy link
Copy Markdown
Owner

@ChrisAdamsdevelopment ChrisAdamsdevelopment commented May 18, 2026

Motivation

  • Fix contradictory UI/backend behavior where Full Server Cleanse was recommended for formats the server actually rejects (MP3/WAV/FLAC) and ensure plan-related rejections are distinct from format rejections.
  • Make server support policy explicit and shared so frontend recommendations always reflect backend capabilities.
  • Surface clearer, actionable error reasons and diagnostic logging so callers and operators can distinguish unsupported formats, plan/usage limits, and processing failures.

Description

  • Added a shared policy module server/cleansePolicy.js containing the canonical supported/recommended sets (quick => .mp3, server => .mp4, .m4a) and helpers normalizeExt / isServerSupportedFormat.
  • Updated POST /api/process in server.js to use the policy helper, emit diagnostic logging (file name, MIME, extension, mode, user plan), and return structured 422 responses with reason: 'unsupported_file_type' and supportedServerFormats when a file is rejected for format.
  • Made free-vs-paid behavior explicit: single-file /api/process remains allowed for free until monthly limit (returns 402 with reason: 'usage_limit'), while batch /api/process-batch remains paid-only and now returns 403 with reason: 'plan_restriction'.
  • Improved backend error classification in server/processor.js by tagging unsupportedCleanseError with reason: 'unsupported_file_type' and adding exiftool_failure for metadata rewrite failures, and propagating reason fields in API error responses.
  • Updated frontend app.tsx UI logic to only recommend/enable Full Server Cleanse for MP4/M4A, to keep Quick Cleanse as MP3-only, and to show clear WAV/FLAC guidance instead of implying server support.
  • Files changed: server/cleansePolicy.js (new), server.js, server/processor.js, app.tsx.

Testing

  • Ran npm run build (automated TypeScript build); build failed with pre-existing frontend TypeScript/React dependency errors unrelated to these changes (missing React/JSX types), so no fully passing build could be produced in this environment.
  • No other automated tests were present/triggered by the patch; API behavior was updated to return explicit JSON reason fields to make automated verification straightforward in downstream tests.

Codex Task

Summary by Sourcery

Align server cleanse behavior, error responses, and UI recommendations with a canonical cleanse policy and actual server format support.

New Features:

  • Introduce a shared cleanse policy module defining supported and recommended formats for quick and server cleanse modes.
  • Return structured error responses with machine-readable reason codes when processing fails or formats are unsupported.

Bug Fixes:

  • Prevent Full Server Cleanse from being offered or attempted for file formats that the server does not actually support (e.g., MP3/WAV/FLAC).

Enhancements:

  • Add diagnostic logging for server cleanse requests and rejections to aid debugging and operations.
  • Classify backend processing failures more precisely, including unsupported file types and ExifTool metadata rewrite failures, and propagate these reasons to API consumers.
  • Clarify frontend messaging around Full Server Cleanse eligibility, free-plan limits, and guidance for WAV/FLAC uploads.

Tests:

  • Expose explicit JSON reason fields in API responses to facilitate future automated testing of error conditions.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 18, 2026

Reviewer's Guide

Introduces a shared cleanse policy module to centralize supported formats, updates backend endpoints to use it for validation and structured error responses with explicit reasons, improves error classification/logging for server processing, and aligns frontend UI recommendations and button states with the actual server-supported formats and plan constraints.

Sequence diagram for POST /api/process with cleanse policy and error reasons

sequenceDiagram
  actor User
  participant BrowserApp
  participant Server
  participant cleansePolicy
  participant DB
  participant processor

  User->>BrowserApp: Click Full Server Cleanse
  BrowserApp->>Server: POST /api/process (file, metadata)
  Server->>DB: SELECT plan FROM users
  DB-->>Server: userPlan
  Server->>cleansePolicy: isServerSupportedFormat(originalName, mime)

  alt [unsupported_file_type]
    Server->>Server: fs.remove(inputPath)
    Server-->>BrowserApp: 422 JSON { reason: unsupported_file_type, supportedServerFormats }
  else [supported_format]
    alt [userPlan is free and limit reached]
      Server->>Server: getMonthlyJobCount(userId)
      Server->>Server: fs.remove(inputPath)
      Server-->>BrowserApp: 402 JSON { reason: usage_limit }
    else [allowed by plan/usage]
      Server->>processor: processMediaFile(outputPath, originalName, platform, metadata)
      alt [exiftool_failure]
        processor->>processor: exiftool.write(...)
        processor-->>Server: throw exiftoolFailureError
        Server-->>BrowserApp: 500 JSON { reason: exiftool_failure }
      else [other processing error]
        processor-->>Server: throw error (may include reason)
        Server-->>BrowserApp: status JSON { reason from error or server_processing_failure }
      end
    end
  end
Loading

File-Level Changes

Change Details Files
Centralized cleanse format policy and helpers for backend/frontend alignment.
  • Added CLEANSE_POLICY describing quick vs server supported/recommended extensions (MP3 for quick, MP4/M4A for server).
  • Implemented normalizeExt and isServerSupportedFormat helpers based on extension and MIME type.
  • Exported policy and helpers for reuse by server routes and (potentially) frontend logic.
server/cleansePolicy.js
Updated single-file server cleanse API to enforce policy, log diagnostics, and return structured reasons.
  • Replaced ad-hoc MP3 check with normalizeExt and isServerSupportedFormat for format validation.
  • On unsupported format, now logs diagnostic context and returns 422 with reason 'unsupported_file_type' plus supportedServerFormats.
  • For free users over the monthly limit, logs usage_limit context and returns 402 with reason 'usage_limit'.
  • On processing errors, propagates err.reason when present and defaults reasons to 'unsupported_file_type' for 422 or 'server_processing_failure' otherwise.
server.js
Clarified batch processing plan gating and per-file format rejection semantics.
  • Kept batch processing Creator/Studio-only but now returns 403 with reason 'plan_restriction' for free users.
  • In batch loop, uses normalizeExt and isServerSupportedFormat instead of MP3-only checks to skip unsupported files.
  • Adds per-file result objects with reason 'unsupported_file_type' when rejected for format while continuing other files.
server.js
Improved backend error typing around unsupported formats and ExifTool failures.
  • Tagged unsupportedCleanseError with reason 'unsupported_file_type' for consistent propagation to API responses.
  • Introduced exiftoolFailureError with status 500 and reason 'exiftool_failure'.
  • Wrapped exiftool.write in try/catch to translate failures into exiftoolFailureError with clear publicDetail.
server/processor.js
Aligned frontend Quick vs Full Server Cleanse behavior and messaging with backend capabilities.
  • Introduced getExt and derived activeExt to classify active file types in the UI.
  • Made Quick Cleanse explicitly MP3-only and Full Server Cleanse only enabled/recommended for MP4/M4A via isServerSupportedFormat flag.
  • Extended serverDisabledReason to cover missing selection and unsupported formats with specific copy.
  • Updated Full Server Cleanse help text to reflect MP4/M4A recommendation and free-plan allowance up to limit.
  • Added explicit guidance for selected WAV/FLAC files suggesting Quick Cleanse or conversion to M4A/MP4.
app.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ChrisAdamsdevelopment ChrisAdamsdevelopment merged commit 49dda35 into main May 18, 2026
4 of 5 checks passed
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The frontend hard-codes isServerSupportedFormat logic (MP4/M4A check) separately from CLEANSE_POLICY.server.supportedExtensions; consider wiring the frontend to consume the shared policy (or an API-provided capability payload) so format support can’t drift between client and server.
  • In POST /api/process, the fallback reason of 'unsupported_file_type' for any 422 without an explicit err.reason may misclassify other validation failures as format issues; it may be safer to default to a generic reason or map only known error types explicitly.
  • Batch processing responses mix structured errors (with reason) and plain error strings depending on the failure path; you might want to include a reason field consistently in all batch result error objects to make downstream handling more uniform.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The frontend hard-codes `isServerSupportedFormat` logic (MP4/M4A check) separately from `CLEANSE_POLICY.server.supportedExtensions`; consider wiring the frontend to consume the shared policy (or an API-provided capability payload) so format support can’t drift between client and server.
- In `POST /api/process`, the fallback `reason` of `'unsupported_file_type'` for any 422 without an explicit `err.reason` may misclassify other validation failures as format issues; it may be safer to default to a generic reason or map only known error types explicitly.
- Batch processing responses mix structured errors (with `reason`) and plain error strings depending on the failure path; you might want to include a `reason` field consistently in all batch result error objects to make downstream handling more uniform.

## Individual Comments

### Comment 1
<location path="server.js" line_range="489-498" />
<code_context>
+  const ext = normalizeExt(originalName);
</code_context>
<issue_to_address>
**issue (bug_risk):** Normalizing the extension to empty string can lead to extension-less server output files in some cases.

With `normalizeExt` now returning `''`, a file uploaded without an extension but with a supported MIME type will still pass `isServerSupportedFormat`, but `outputPath` will append an empty `ext`. That yields output files with no extension, which is problematic for users and some tools. Please add a fallback to a safe default extension (e.g. `.mp4`) when `normalizeExt` returns an empty string but the MIME type is supported.
</issue_to_address>

### Comment 2
<location path="server.js" line_range="548-550" />
<code_context>
+    const ext = normalizeExt(file.originalname || '');
</code_context>
<issue_to_address>
**issue (bug_risk):** Batch output files may lose their default extension, unlike the previous behavior.

Previously, when `path.extname` returned an empty string in the batch path, `ext` defaulted to `.mp4`. With `normalizeExt`, `ext` is now `''` in that case, so extension-less uploads will produce output paths with no file extension, changing the prior behavior and affecting download names. Please consider a fallback extension here (as in the single-file route) when `ext` is empty but `mime` is a supported format.
</issue_to_address>

### Comment 3
<location path="server/cleansePolicy.js" line_range="24-30" />
<code_context>
+  const safeMime = String(mime || '').toLowerCase();
+  if (CLEANSE_POLICY.server.supportedExtensions.includes(ext)) return true;
+  // Common upload MIME aliases for supported server formats.
+  return safeMime === 'video/mp4' || safeMime === 'audio/mp4' || safeMime === 'audio/x-m4a';
+}
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The server MIME checks for M4A may miss common variants such as `audio/m4a`.

The guard only allows `video/mp4`, `audio/mp4`, and `audio/x-m4a`, but some clients send M4A as `audio/m4a`. To prevent valid M4A uploads from being rejected when MIME/ext don’t line up, please add `audio/m4a` (and any other expected aliases) to this list.

```suggestion
function isServerSupportedFormat(filename = '', mime = '') {
  const ext = normalizeExt(filename);
  const safeMime = String(mime || '').toLowerCase();
  if (CLEANSE_POLICY.server.supportedExtensions.includes(ext)) return true;
  // Common upload MIME aliases for supported server formats.
  return (
    safeMime === 'video/mp4' ||
    safeMime === 'audio/mp4' ||
    safeMime === 'audio/x-m4a' ||
    safeMime === 'audio/m4a'
  );
}
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread server.js
Comment on lines +489 to +498
const ext = normalizeExt(originalName);
const mime = (req.file.mimetype || '').toLowerCase();
const isMp3 = ext === '.mp3' || mime === 'audio/mpeg';
if (isMp3) {
await fs.remove(inputPath).catch(() => {});
return res.status(422).json({ error: 'MP3 server cleanse is not supported', detail: 'Use Quick Cleanse (Browser) for MP3. Full Server Cleanse is best supported for MP4/M4A; WAV/FLAC may be rejected if ExifTool cannot safely rewrite them.' });
}
const dbUser = db.prepare('SELECT plan FROM users WHERE id = ?').get(userId);
const userPlan = dbUser?.plan ?? 'free';
console.info('[process] request', { fileName: originalName, mime, extension: ext || '(none)', mode: 'server', userPlan });
if (!isServerSupportedFormat(originalName, mime)) {
await fs.remove(inputPath).catch(() => {});
console.info('[process] rejected', { reason: 'unsupported_file_type', extension: ext || '(none)', mime, userPlan });
return res.status(422).json({
error: 'Unsupported file type for Full Server Cleanse',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Normalizing the extension to empty string can lead to extension-less server output files in some cases.

With normalizeExt now returning '', a file uploaded without an extension but with a supported MIME type will still pass isServerSupportedFormat, but outputPath will append an empty ext. That yields output files with no extension, which is problematic for users and some tools. Please add a fallback to a safe default extension (e.g. .mp4) when normalizeExt returns an empty string but the MIME type is supported.

Comment thread server.js
Comment on lines +548 to +550
const ext = normalizeExt(file.originalname || '');
const mime = (file.mimetype || '').toLowerCase();
const isMp3 = ext === '.mp3' || mime === 'audio/mpeg';
if (isMp3) { await fs.remove(file.path).catch(() => {}); results.push({ originalName: file.originalname, error: 'MP3 server cleanse is not supported. Use Quick Cleanse (Browser) for MP3.' }); continue; }
if (!isServerSupportedFormat(file.originalname || '', mime)) { await fs.remove(file.path).catch(() => {}); results.push({ originalName: file.originalname, error: 'Full Server Cleanse currently supports MP4 and M4A only. Use Quick Cleanse (Browser) for MP3, or convert WAV/FLAC to M4A/MP4.', reason: 'unsupported_file_type' }); continue; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Batch output files may lose their default extension, unlike the previous behavior.

Previously, when path.extname returned an empty string in the batch path, ext defaulted to .mp4. With normalizeExt, ext is now '' in that case, so extension-less uploads will produce output paths with no file extension, changing the prior behavior and affecting download names. Please consider a fallback extension here (as in the single-file route) when ext is empty but mime is a supported format.

Comment thread server/cleansePolicy.js
Comment on lines +24 to +30
function isServerSupportedFormat(filename = '', mime = '') {
const ext = normalizeExt(filename);
const safeMime = String(mime || '').toLowerCase();
if (CLEANSE_POLICY.server.supportedExtensions.includes(ext)) return true;
// Common upload MIME aliases for supported server formats.
return safeMime === 'video/mp4' || safeMime === 'audio/mp4' || safeMime === 'audio/x-m4a';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): The server MIME checks for M4A may miss common variants such as audio/m4a.

The guard only allows video/mp4, audio/mp4, and audio/x-m4a, but some clients send M4A as audio/m4a. To prevent valid M4A uploads from being rejected when MIME/ext don’t line up, please add audio/m4a (and any other expected aliases) to this list.

Suggested change
function isServerSupportedFormat(filename = '', mime = '') {
const ext = normalizeExt(filename);
const safeMime = String(mime || '').toLowerCase();
if (CLEANSE_POLICY.server.supportedExtensions.includes(ext)) return true;
// Common upload MIME aliases for supported server formats.
return safeMime === 'video/mp4' || safeMime === 'audio/mp4' || safeMime === 'audio/x-m4a';
}
function isServerSupportedFormat(filename = '', mime = '') {
const ext = normalizeExt(filename);
const safeMime = String(mime || '').toLowerCase();
if (CLEANSE_POLICY.server.supportedExtensions.includes(ext)) return true;
// Common upload MIME aliases for supported server formats.
return (
safeMime === 'video/mp4' ||
safeMime === 'audio/mp4' ||
safeMime === 'audio/x-m4a' ||
safeMime === 'audio/m4a'
);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant