Skip to content

refactor(upload): implement dynamic max file upload size configuration and handle upload size exceptions#362

Draft
balazs-szucs wants to merge 4 commits intogrimmory-tools:developfrom
balazs-szucs:max-upload-size
Draft

refactor(upload): implement dynamic max file upload size configuration and handle upload size exceptions#362
balazs-szucs wants to merge 4 commits intogrimmory-tools:developfrom
balazs-szucs:max-upload-size

Conversation

@balazs-szucs
Copy link
Copy Markdown
Member

@balazs-szucs balazs-szucs commented Apr 3, 2026

Description

Linked Issue: Fixes #152

Changes

This pull request refactors how the maximum file upload size is configured and enforced in the application. The upload size limit is now consistently determined by the MAX_UPLOAD_SIZE environment variable (or the Spring Boot property), and enforced at the framework level, rather than in application logic. Related error handling, configuration, and user interface messaging have been updated to reflect this change.

Summary by CodeRabbit

  • New Features

    • File upload size is now configurable via the MAX_UPLOAD_SIZE environment variable at server startup (default 100MB).
  • Bug Fixes

    • Oversized uploads now return clearer "payload too large" responses with the server limit shown.
  • Documentation

    • Added an informative note in global settings explaining that upload size changes require updating the env var and restarting the server.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f53dc3f3-f6a2-4d7a-b16d-a8d088e89719

📥 Commits

Reviewing files that changed from the base of the PR and between b2fb1c8 and d1ad4b6.

📒 Files selected for processing (1)
  • booklore-api/src/main/java/org/booklore/service/upload/FileUploadService.java
💤 Files with no reviewable changes (1)
  • booklore-api/src/main/java/org/booklore/service/upload/FileUploadService.java
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)

📝 Walkthrough

Walkthrough

Consolidates upload-size configuration into Spring's multipart properties (driven by MAX_UPLOAD_SIZE), removes service-layer size checks, adds a global handler for MaxUploadSizeExceededException (HTTP 413), updates controller to read size from AppSettingService, and surfaces an informational UI message about the env var.

Changes

Cohort / File(s) Summary
Configuration & Settings
booklore-api/src/main/resources/application.yaml, booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java
Switch multipart limits to ${MAX_UPLOAD_SIZE:100MB} and inject spring.servlet.multipart.max-file-size into AppSettingService; expose getMaxUploadSizeMb() and stop reading persisted MAX_FILE_UPLOAD_SIZE.
Exception Handling
booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java
Add handler for MaxUploadSizeExceededException that returns HTTP 413 with a message including server-configured max upload size.
Controller & Service
booklore-api/src/main/java/org/booklore/controller/AppUserController.java, booklore-api/src/main/java/org/booklore/service/upload/FileUploadService.java
Remove in-service file-size validation and controller fallback logic; AppUserController.getCurrentUser() now uses AppSettingService.getMaxUploadSizeMb() directly.
Tests
booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java
Remove AppSettingService mocking and the test asserting service-level file-too-large behavior.
Frontend UI & i18n
frontend/src/app/features/settings/global-preferences/global-preferences.component.html, frontend/src/app/features/settings/global-preferences/global-preferences.component.scss, frontend/src/i18n/en/settings-application.json
Add an informational message, styles, and translation key explaining that max upload size is controlled by MAX_UPLOAD_SIZE and requires server restart.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SpringMVC as Server (Spring)
    participant AppController as AppUserController
    participant AppSetting as AppSettingService
    participant UploadService as FileUploadService
    participant GlobalHandler as GlobalExceptionHandler
    Client->>SpringMVC: POST /upload (multipart)
    alt Request size <= spring.servlet.multipart.max-file-size
        SpringMVC->>AppController: parsed request with file
        AppController->>AppSetting: getMaxUploadSizeMb()
        AppController->>UploadService: validateFile(file)
        UploadService-->>AppController: validation OK
        AppController->>SpringMVC: proceed with upload processing
        SpringMVC-->>Client: 200 OK
    else Request size > spring.servlet.multipart.max-file-size
        SpringMVC--xClient: parsing fails (MaxUploadSizeExceeded)
        SpringMVC->>GlobalHandler: MaxUploadSizeExceededException
        GlobalHandler-->>Client: 413 Payload Too Large (message with configured MB)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • zachyale

"I'm a rabbit in the code-strewn glade,
MAX_UPLOAD_SIZE now wisely laid.
Spring guards the stream, handlers in place,
No more surprises mid-uploaded race. 🐇✨"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows conventional commit format with type(scope): description, clearly summarizing the core change.
Description check ✅ Passed The description includes all required sections with sufficient detail about what the PR does and links to issue #152.
Linked Issues check ✅ Passed Code changes align with issue #152 objectives: max upload size is now environment-variable-driven and enforced at framework level rather than application logic.
Out of Scope Changes check ✅ Passed All changes directly address the linked issue; frontend updates document the environment variable requirement, and backend changes implement the dynamic configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/src/app/features/settings/global-preferences/global-preferences.component.html (1)

191-194: Good informational message, but consider making the input read-only.

The info message explains the environment variable, but the input field and save button remain enabled (lines 173-180). Users can still edit and "save" a value that won't take effect, which may cause confusion.

Consider making the input [readonly]="true" or [disabled]="true" to clearly indicate it's display-only, with a link or instruction to modify the Docker/environment configuration instead.

♻️ Suggested change to make input read-only
                  <input
                    type="text"
                    pInputText
                    [(ngModel)]="maxFileUploadSizeInMb"
+                   [readonly]="true"
                    [placeholder]="t('fileManagement.maxUploadPlaceholder')"/>

And hide or disable the save button for this field.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/settings/global-preferences/global-preferences.component.html`
around lines 191 - 194, The environment-variable UI is currently editable even
though it’s informational; update global-preferences.component.html so the
env-var input element is display-only (add [readonly]="true" or
[disabled]="true" on the specific input used for the environment variable) and
prevent saving by disabling or hiding the associated save button for that field;
ensure you target the input and save button for the env var displayed next to
the info-message (the element showing t('fileManagement.envVarNote')) so users
see it’s read-only and are guided to change the value via Docker/environment
config instead.
frontend/src/app/features/settings/global-preferences/global-preferences.component.scss (1)

162-196: Optional: Consider extracting shared base styles.

.warning-message and .info-message are nearly identical, differing only in icon color. You could extract a shared %message-base placeholder to reduce duplication.

♻️ Optional DRY refactor
+%message-base {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  font-size: 0.875rem;
+  color: var(--p-text-muted-color);
+  margin-top: 0.5rem;
+
+  .pi {
+    font-size: settings.$settings-section-description-size;
+  }
+
+  `@media` (width <= 768px) {
+    margin-top: 0.5rem;
+  }
+}
+
 .warning-message {
-  display: flex;
-  align-items: center;
-  gap: 0.5rem;
-  font-size: 0.875rem;
-  color: var(--p-text-muted-color);
-  margin-top: 0.5rem;
+  `@extend` %message-base;

   .pi {
     color: var(--p-orange-500);
-    font-size: settings.$settings-section-description-size;
-  }
-
-  `@media` (width <= 768px) {
-    margin-top: 0.5rem;
   }
 }

 .info-message {
-  display: flex;
-  align-items: center;
-  gap: 0.5rem;
-  font-size: 0.875rem;
-  color: var(--p-text-muted-color);
-  margin-top: 0.5rem;
+  `@extend` %message-base;

   .pi {
     color: var(--p-blue-500);
-    font-size: settings.$settings-section-description-size;
-  }
-
-  `@media` (width <= 768px) {
-    margin-top: 0.5rem;
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/src/app/features/settings/global-preferences/global-preferences.component.scss`
around lines 162 - 196, Extract the duplicated rules from .warning-message and
.info-message into a shared SCSS placeholder (e.g. %message-base) containing
display, align-items, gap, font-size, color, margin-top, and the `@media` block;
then change .warning-message and .info-message to `@extend` %message-base and only
keep their unique .pi color and font-size override (leave the .pi selector in
each to set var(--p-orange-500) and var(--p-blue-500) respectively). This
reduces duplication while preserving the existing selectors and responsive
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java`:
- Line 183: The UI allows saving MAX_FILE_UPLOAD_SIZE_IN_MB but
buildAppSettings() (AppSettingService) always uses the Spring-injected
maxUploadSizeMb (builder.maxFileUploadSizeInMb(maxUploadSizeMb)), making saved
values ineffective; fix by either (A) remove
AppSettingKey.MAX_FILE_UPLOAD_SIZE_IN_MB from AppSettingKey and update the
frontend to hide/disable the field and save button for that setting, or (B) keep
the key but make the frontend input read-only/disabled for
AppSettingKey.MAX_FILE_UPLOAD_SIZE_IN_MB (and disable calls to updateSetting()
for it) so the UI reflects that the value is environment-controlled; ensure
AppSettingService.buildAppSettings() continues to use the injected
maxUploadSizeMb and do not attempt to persist or apply the saved value.

---

Nitpick comments:
In
`@frontend/src/app/features/settings/global-preferences/global-preferences.component.html`:
- Around line 191-194: The environment-variable UI is currently editable even
though it’s informational; update global-preferences.component.html so the
env-var input element is display-only (add [readonly]="true" or
[disabled]="true" on the specific input used for the environment variable) and
prevent saving by disabling or hiding the associated save button for that field;
ensure you target the input and save button for the env var displayed next to
the info-message (the element showing t('fileManagement.envVarNote')) so users
see it’s read-only and are guided to change the value via Docker/environment
config instead.

In
`@frontend/src/app/features/settings/global-preferences/global-preferences.component.scss`:
- Around line 162-196: Extract the duplicated rules from .warning-message and
.info-message into a shared SCSS placeholder (e.g. %message-base) containing
display, align-items, gap, font-size, color, margin-top, and the `@media` block;
then change .warning-message and .info-message to `@extend` %message-base and only
keep their unique .pi color and font-size override (leave the .pi selector in
each to set var(--p-orange-500) and var(--p-blue-500) respectively). This
reduces duplication while preserving the existing selectors and responsive
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f56a2617-9e29-4f70-be0c-12af241168ae

📥 Commits

Reviewing files that changed from the base of the PR and between 61bbd19 and f32a38f.

📒 Files selected for processing (9)
  • booklore-api/src/main/java/org/booklore/app/controller/AppUserController.java
  • booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java
  • booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java
  • booklore-api/src/main/java/org/booklore/service/upload/FileUploadService.java
  • booklore-api/src/main/resources/application.yaml
  • booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java
  • frontend/src/app/features/settings/global-preferences/global-preferences.component.html
  • frontend/src/app/features/settings/global-preferences/global-preferences.component.scss
  • frontend/src/i18n/en/settings-application.json
💤 Files with no reviewable changes (1)
  • booklore-api/src/main/java/org/booklore/service/upload/FileUploadService.java
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Packaging Smoke Test
🧰 Additional context used
📓 Path-based instructions (4)
frontend/src/**/*.{ts,tsx,html,scss}

📄 CodeRabbit inference engine (AGENTS.md)

Use 2-space indentation in TypeScript, HTML, and SCSS files

Files:

  • frontend/src/app/features/settings/global-preferences/global-preferences.component.html
  • frontend/src/app/features/settings/global-preferences/global-preferences.component.scss
frontend/src/i18n/**

📄 CodeRabbit inference engine (AGENTS.md)

Put user-facing strings in Transloco files under frontend/src/i18n/

Files:

  • frontend/src/i18n/en/settings-application.json
booklore-api/src/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

booklore-api/src/**/*.java: Use 4-space indentation in Java files and match surrounding Java style
Prefer constructor injection via Lombok patterns already used in the codebase. Do not introduce @Autowired field injection
Use MapStruct for entity/DTO mapping

Files:

  • booklore-api/src/main/java/org/booklore/app/controller/AppUserController.java
  • booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java
  • booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java
  • booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java
booklore-api/src/test/**/*.java

📄 CodeRabbit inference engine (AGENTS.md)

Prefer focused unit tests; use @SpringBootTest only when the Spring context is required

Files:

  • booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java
🧠 Learnings (3)
📚 Learning: 2026-03-31T14:12:39.521Z
Learnt from: alexhb1
Repo: grimmory-tools/grimmory PR: 307
File: frontend/src/i18n/hr/layout.json:3-3
Timestamp: 2026-03-31T14:12:39.521Z
Learning: In `frontend/src/i18n/hr/layout.json` (Croatian locale), newly added i18n keys may intentionally use English placeholder values (e.g., `toggleNavigation`, `adjustSidebarWidth`, `closeNavigation`). Do not flag these as missing Croatian translations — this is a deliberate placeholder approach by the team.

Applied to files:

  • frontend/src/i18n/en/settings-application.json
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/test/**/*.java : Prefer focused unit tests; use `SpringBootTest` only when the Spring context is required

Applied to files:

  • booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java
📚 Learning: 2026-03-26T01:46:48.863Z
Learnt from: CR
Repo: grimmory-tools/grimmory PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-26T01:46:48.863Z
Learning: Applies to booklore-api/src/**/*.java : Use MapStruct for entity/DTO mapping

Applied to files:

  • booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java
🔇 Additional comments (8)
booklore-api/src/main/resources/application.yaml (1)

47-48: Solid approach to centralize upload size configuration.

Using a single environment variable for both max-file-size and max-request-size ensures consistency and directly addresses the root cause of issue #152 where the GUI setting wasn't propagating to Tomcat's multipart limits.

booklore-api/src/test/java/org/booklore/service/upload/FileUploadServiceTest.java (2)

83-86: Constructor update aligns with refactored service dependencies.

The removal of AppSettingService from the constructor is consistent with the architectural change moving file size validation to Spring's multipart handling.


83-86: FILE_TOO_LARGE is actively used and should not be removed.

FILE_TOO_LARGE is referenced in BookCoverService.java:500 and remains in active use. The constant should not be removed.

			> Likely an incorrect or invalid review comment.
booklore-api/src/main/java/org/booklore/exception/GlobalExceptionHandler.java (1)

80-88: Well-implemented exception handler for oversized uploads.

The handler correctly:

  • Returns HTTP 413 (PAYLOAD_TOO_LARGE) per RFC 7231
  • Gracefully handles edge cases where maxUploadSize is unknown (≤0)
  • Provides an informative message with the configured limit
booklore-api/src/main/java/org/booklore/service/appsettings/AppSettingService.java (1)

41-56: Clean injection of Spring's multipart configuration.

Using DataSize for parsing and converting at startup is efficient. The implementation correctly derives the value from the same property that Spring uses for enforcement.

booklore-api/src/main/java/org/booklore/app/controller/AppUserController.java (1)

26-32: Cleaner implementation using centralized service method.

Directly calling appSettingService.getMaxUploadSizeMb() removes the previous error-swallowing try/catch and properly delegates configuration retrieval to the service layer.

frontend/src/i18n/en/settings-application.json (1)

35-35: Clear, actionable guidance for users.

The message properly explains the environment variable control and the restart requirement, helping users understand how to actually change the limit.

frontend/src/app/features/settings/global-preferences/global-preferences.component.scss (1)

180-196: Consistent styling matching the existing warning message pattern.

The .info-message class appropriately uses blue for informational context (vs. orange for warnings). The implementation is consistent with the existing .warning-message styling.

@balazs-szucs balazs-szucs marked this pull request as ready for review April 3, 2026 17:42
Copy link
Copy Markdown
Member

@zachyale zachyale left a comment

Choose a reason for hiding this comment

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

Image

We have to think this through better- currently on this branch, the UI pre-fills the value via the ENV var by default, but allows users to edit it and hit save (updating the setting value instead). Refreshing this page then just re-initializes the value in that box to the current ENV var.

I still think we should be highlighting this not as an ENV var, but as a user defined setting, but regardless of whether we pivot to that or keep the ENV var, we need to fix this UI so it makes more sense for whichever direction we go. Discuss tomorrow?

@zachyale
Copy link
Copy Markdown
Member

zachyale commented Apr 9, 2026

As per our maintainer sync up, let's drive this PR to be a full ENV var approach, and freeze this work being merged until after this upcoming release.

@balazs-szucs balazs-szucs marked this pull request as draft April 9, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File Upload Limits not Respected for Files over 1GB

2 participants