Skip to content

FEATURE: automatically downsize large images#1

Open
ShashankFC wants to merge 1 commit into
image-processing-optimizationfrom
large-image-processing
Open

FEATURE: automatically downsize large images#1
ShashankFC wants to merge 1 commit into
image-processing-optimizationfrom
large-image-processing

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Jan 5, 2026

Copy link
Copy Markdown
Collaborator

Test 1

Summary by CodeRabbit

  • New Features

    • Images exceeding the upload size limit are now automatically downsized to fit within allowed limits.
  • Refactor

    • Optimized image upload handling with improved automatic resizing capabilities.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/discourse-coderabbit#1

@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 6, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 6, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Fixed 10 MB client-side upload validation limit and implemented server-side image downscaling. Added OptimizedImage.downsize() method and refactored the optimize() API to accept a single dimensions parameter instead of separate width/height values.

Changes

Cohort / File(s) Summary
Client-side upload validation
app/assets/javascripts/discourse/lib/utilities.js
Replaced dynamic maximum upload size calculations with fixed 10 MB (10 \* 1024) limits in validateUploadedFile and displayErrorForUpload methods.
Server-side image downscaling
app/controllers/uploads_controller.rb
Added automatic image downscaling logic for downloads/uploads exceeding max_image_size_kb. Implements up to 5 downscaling iterations using OptimizedImage.downsize() at 80% quality, respecting allow_animated_thumbnails setting.
Image optimization API refactoring
app/models/optimized_image.rb
Added new downsize() public method; refactored optimize() signature to accept single dimensions parameter instead of separate width/height; removed dimensions() helper; updated all internal resizing calls to construct "WIDTHxHEIGHT" format strings.

Sequence Diagram

sequenceDiagram
    participant User
    participant UploadsController as Controller
    participant OptimizedImage
    participant FileSystem as File System

    User->>UploadsController: Upload/download image
    UploadsController->>UploadsController: Check if image exceeds<br/>max_image_size_kb
    alt Image exceeds limit
        loop Up to 5 iterations
            UploadsController->>OptimizedImage: downsize(file, dimensions)<br/>with 80% quality
            OptimizedImage->>FileSystem: Resize image in-place
            OptimizedImage-->>UploadsController: Updated file size
            UploadsController->>UploadsController: Check if within limit
        end
    end
    UploadsController->>FileSystem: Save final image
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Ten megs we cap with steadfast grace,
No oversized uploads in this place—
Big images shrink with downsize spree,
Five tries to fit them just-right-ly! 🖼️✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'FEATURE: automatically downsize large images' directly describes the main change across the pull request—adding automatic image downsizing functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 13, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @app/controllers/uploads_controller.rb:
- Line 72: The call to Upload.create_for uses tempfile.size which may be stale
after the downsizing loop; replace that argument with the actual on-disk size by
calling File.size(tempfile.path) (and ensure the tempfile is flushed/rewound if
needed) when invoking Upload.create_for(current_user.id, tempfile, filename,
File.size(tempfile.path), content_type: content_type, image_type: type) so the
stored size reflects the post-downsize file; locate the call to
Upload.create_for in the uploads controller and update the size parameter
accordingly.
🧹 Nitpick comments (2)
app/assets/javascripts/discourse/lib/utilities.js (1)

245-248: Wrap switch case declaration in a block to prevent scope leakage.

The var maxSizeKB declaration inside the case 413 block can be accessed by other switch clauses due to JavaScript's hoisting behavior. Wrap in a block for proper scoping.

♻️ Proposed fix
         // entity too large, usually returned from the web server
-        case 413:
-          var maxSizeKB = 10 * 1024; // 10 MB
+        case 413: {
+          var maxSizeKB = 10 * 1024; // 10 MB
           bootbox.alert(I18n.t('post.errors.file_too_large', { max_size_kb: maxSizeKB }));
           return;
+        }
app/controllers/uploads_controller.rb (1)

54-56: Avoid rescue modifier — exceptions are silently swallowed.

The rescue nil modifier hides all download failures, making debugging difficult. Use explicit error handling to log failures.

♻️ Proposed fix
       if file.nil? && url.present? && is_api?
-        tempfile = FileHelper.download(url, 10.megabytes, "discourse-upload-#{type}") rescue nil
+        begin
+          tempfile = FileHelper.download(url, 10.megabytes, "discourse-upload-#{type}")
+        rescue => e
+          Rails.logger.warn("Failed to download upload from URL #{url}: #{e.message}")
+          tempfile = nil
+        end
         filename = File.basename(URI.parse(url).path)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f71fa1 and ffbaf8c.

📒 Files selected for processing (3)
  • app/assets/javascripts/discourse/lib/utilities.js
  • app/controllers/uploads_controller.rb
  • app/models/optimized_image.rb
🧰 Additional context used
🧬 Code graph analysis (1)
app/controllers/uploads_controller.rb (1)
app/models/optimized_image.rb (2)
  • downsize (145-147)
  • downsize (149-151)
🪛 Biome (2.1.2)
app/assets/javascripts/discourse/lib/utilities.js

[error] 246-246: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🪛 RuboCop (1.82.1)
app/controllers/uploads_controller.rb

[convention] 55-55: Avoid using rescue in its modifier form.

(Style/RescueModifier)

app/models/optimized_image.rb

[warning] 149-149: Method OptimizedImage.downsize is defined at both app/models/optimized_image.rb:145 and app/models/optimized_image.rb:149.

(Lint/DuplicateMethods)

🔇 Additional comments (4)
app/models/optimized_image.rb (3)

141-143: LGTM!

The resize method correctly constructs the dimensions string and delegates to optimize.


153-158: LGTM!

The optimize method signature update to accept a unified dimensions parameter is clean and the dynamic method dispatch for animated vs non-animated instructions works correctly.


145-151: Critical: Duplicate method definitions — second downsize overwrites the first.

Ruby does not support method overloading. The second downsize definition (lines 149-151) completely replaces the first one (lines 145-147). Any code calling the 4-argument signature downsize(from, to, max_width, max_height, opts) will fail with an ArgumentError. Verify that all call sites use only the dimensions-string signature to confirm this change is intentional.

app/assets/javascripts/discourse/lib/utilities.js (1)

182-186: Hard-coded upload limit removes per-type flexibility.

The change from Discourse.SiteSettings['max_' + type + '_size_kb'] to a fixed 10,240 KB limit removes the ability to configure different size thresholds for images versus attachments. While this aligns with the server-side 10 MB fixed limit, consider verifying that no existing administrator configurations depend on per-type limits and that the old settings have been fully deprecated.

Comment on lines +63 to +70
# allow users to upload large images that will be automatically reduced to allowed size
if tempfile && tempfile.size > 0 && SiteSetting.max_image_size_kb > 0 && FileHelper.is_image?(filename)
attempt = 5
while attempt > 0 && tempfile.size > SiteSetting.max_image_size_kb.kilobytes
OptimizedImage.downsize(tempfile.path, tempfile.path, "80%", allow_animation: SiteSetting.allow_animated_thumbnails)
attempt -= 1
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does ImageMagick convert -resize accept percentage values like "80%"?

💡 Result:

Yes. -resize accepts percentage values (e.g. magick input.png -resize 80% output.png). [1][2]

Note: on Windows batch files you must escape % by doubling it (use %%). [1]


🏁 Script executed:

# Search for OptimizedImage.downsize implementation
fd -type f -name "*.rb" | xargs rg -l "OptimizedImage" | head -20

Repository: AI-Code-Review-Evals/coderabbit-discourse

Length of output: 226


🏁 Script executed:

# Find the downsize method definition
rg "def downsize" -A 15

Repository: AI-Code-Review-Evals/coderabbit-discourse

Length of output: 67


Improve error handling and file size tracking in the downsize loop.

The downsizing logic has several issues:

  1. No error handling: If OptimizedImage.downsize fails, the loop continues without logging or indication of failure.

  2. File size not refreshed after modification: tempfile.size may not reflect the updated file size after in-place modification. Use File.size(tempfile.path) to get the current file size on disk.

  3. In-place file modification: Using the same path for source and destination carries inherent risk if the operation fails mid-write, potentially corrupting the file.

The "80%" dimension string is a valid ImageMagick resize format and will correctly reduce dimensions by 20%.

🐛 Proposed fix
       # allow users to upload large images that will be automatically reduced to allowed size
       if tempfile && tempfile.size > 0 && SiteSetting.max_image_size_kb > 0 && FileHelper.is_image?(filename)
         attempt = 5
-        while attempt > 0 && tempfile.size > SiteSetting.max_image_size_kb.kilobytes
-          OptimizedImage.downsize(tempfile.path, tempfile.path, "80%", allow_animation: SiteSetting.allow_animated_thumbnails)
+        while attempt > 0 && File.size(tempfile.path) > SiteSetting.max_image_size_kb.kilobytes
+          unless OptimizedImage.downsize(tempfile.path, tempfile.path, "80%", allow_animation: SiteSetting.allow_animated_thumbnails)
+            Rails.logger.warn("Failed to downsize image #{filename}, attempt #{6 - attempt}")
+            break
+          end
           attempt -= 1
         end
       end

end
end

upload = Upload.create_for(current_user.id, tempfile, filename, tempfile.size, content_type: content_type, image_type: type)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

File size passed to Upload.create_for may be stale after downsizing.

After the downsizing loop modifies the file, tempfile.size may not reflect the new size. Use File.size(tempfile.path) to get the accurate post-downsize file size.

🐛 Proposed fix
-      upload = Upload.create_for(current_user.id, tempfile, filename, tempfile.size, content_type: content_type, image_type: type)
+      upload = Upload.create_for(current_user.id, tempfile, filename, File.size(tempfile.path), content_type: content_type, image_type: type)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
upload = Upload.create_for(current_user.id, tempfile, filename, tempfile.size, content_type: content_type, image_type: type)
upload = Upload.create_for(current_user.id, tempfile, filename, File.size(tempfile.path), content_type: content_type, image_type: type)
🤖 Prompt for AI Agents
In @app/controllers/uploads_controller.rb at line 72, The call to
Upload.create_for uses tempfile.size which may be stale after the downsizing
loop; replace that argument with the actual on-disk size by calling
File.size(tempfile.path) (and ensure the tempfile is flushed/rewound if needed)
when invoking Upload.create_for(current_user.id, tempfile, filename,
File.size(tempfile.path), content_type: content_type, image_type: type) so the
stored size reflects the post-downsize file; locate the call to
Upload.create_for in the uploads controller and update the size parameter
accordingly.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants