Skip to content

RE1-T115 S3 api fix#352

Merged
ucswift merged 2 commits intomasterfrom
develop
May 2, 2026
Merged

RE1-T115 S3 api fix#352
ucswift merged 2 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented May 2, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Improved S3 storage reliability with enhanced error handling and fallback upload mechanisms.
    • Refined transient error detection for more robust file upload operations.

@request-info
Copy link
Copy Markdown

request-info Bot commented May 2, 2026

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

@ucswift has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 54 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5a90b756-9022-4a98-986d-8b46c87791d2

📥 Commits

Reviewing files that changed from the base of the PR and between 4600e0f and bb27a61.

📒 Files selected for processing (2)
  • Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs
  • Web/Resgrid.Web.Tts/Services/S3StorageService.cs
📝 Walkthrough

Walkthrough

This PR implements a presigned PUT fallback mechanism for S3 uploads. When the SDK's PutObjectAsync response cannot be parsed (FormatException), the service now checks object existence and falls back to uploading via a presigned URL using HttpClient, with transient retry handling built in.

Changes

S3 Presigned PUT Fallback

Layer / File(s) Summary
Core Service Implementation
Web/Resgrid.Web.Tts/Services/S3StorageService.cs
Constructor now accepts IHttpClientFactory. UploadAsync wraps SDK upload in try-catch for FormatException, delegating to HandleMalformedPutResponseAsync which verifies object persistence. If missing, UploadWithPresignedUrlAsync uploads via presigned URL with HTTP client and transient retry loop. New IsTransientStatusCode helper consolidates transient classification.
Dependency Wiring
Web/Resgrid.Web.Tts/Program.cs
Registers HttpClientFactory via builder.Services.AddHttpClient().
Tests & Validation
Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs
Refactored existing test to use new CreateService helper. Added two tests: (1) FormatException with existing object is treated as success; (2) FormatException with missing object triggers presigned PUT fallback. Introduced RecordingHttpMessageHandler to capture and validate HTTP requests.

Sequence Diagram

sequenceDiagram
    participant Client
    participant S3Service as S3StorageService
    participant S3SDK as S3 SDK<br/>(PutObjectAsync)
    participant MetadataCheck as GetObjectMetadata
    participant PresignedHTTP as Presigned PUT<br/>(HttpClient)

    Client->>S3Service: UploadAsync(stream, key)
    S3Service->>S3SDK: PutObjectAsync
    S3SDK-->>S3Service: FormatException
    S3Service->>MetadataCheck: GetObjectMetadataAsync
    alt Object exists
        MetadataCheck-->>S3Service: Success
        S3Service-->>Client: Success
    else Object missing
        MetadataCheck-->>S3Service: NotFound
        S3Service->>PresignedHTTP: Presigned PUT<br/>(with retry on transient)
        PresignedHTTP-->>S3Service: Success
        S3Service-->>Client: Success
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title 'RE1-T115 S3 api fix' is vague and non-descriptive, using only a ticket reference and generic 'fix' terminology without conveying the specific nature of the changes. Replace with a specific description of the main change, e.g., 'Add presigned PUT fallback for S3 upload failures' or 'S3StorageService: implement fallback handling for malformed PUT responses'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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
Review rate limit: 0/1 reviews remaining, refill in 50 minutes and 54 seconds.

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 (1)
Web/Resgrid.Web.Tts/Services/S3StorageService.cs (1)

15-15: ⚡ Quick win

Use the configured presigned URL lifetime for the fallback PUT.

Web/Resgrid.Web.Tts/Configuration/S3StorageOptions.cs already exposes PresignedUrlExpiryMinutes, and GetObjectUrlAsync honors it. Hardcoding 5 minutes here makes the recovery path ignore that deployment setting, which can still break uploads in environments that need a longer validity window for clock skew or slower S3-compatible backends.

🔧 Proposed fix
-		private const int PresignedPutUrlExpiryMinutes = 5;
@@
 		private string CreatePresignedPutUrl(string objectKey, string contentType)
 		{
 			return _s3Client.GetPreSignedURL(new GetPreSignedUrlRequest
 			{
 				BucketName = _options.Bucket,
 				Key = objectKey,
 				Verb = HttpVerb.PUT,
 				ContentType = contentType,
-				Expires = DateTime.UtcNow.AddMinutes(PresignedPutUrlExpiryMinutes)
+				Expires = DateTime.UtcNow.AddMinutes(_options.PresignedUrlExpiryMinutes)
 			});
 		}

Also applies to: 219-228

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

In `@Web/Resgrid.Web.Tts/Services/S3StorageService.cs` at line 15, The hardcoded
PresignedPutUrlExpiryMinutes constant in S3StorageService prevents the fallback
PUT presigned URL from using the configured lifetime; replace uses of
PresignedPutUrlExpiryMinutes with the configured value (e.g.,
S3StorageOptions.PresignedUrlExpiryMinutes) accessed from the service's injected
options instance (the same one used by GetObjectUrlAsync) inside
S3StorageService (remove/stop referencing the PresignedPutUrlExpiryMinutes
constant and use the injected _options.PresignedUrlExpiryMinutes or equivalent)
so both the normal and fallback PUT URL generation honor the deployment setting;
apply this change to all occurrences (including the block around the
GetObjectUrlAsync fallback at lines ~219-228).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Web/Resgrid.Web.Tts/Services/S3StorageService.cs`:
- Around line 116-145: WasUploadPersistedAsync currently catches
AmazonServiceException, HttpRequestException, and IOException but not non-user
cancellation timeouts; add a catch for TaskCanceledException in
WasUploadPersistedAsync (the same method that calls ExistsAsync) that logs a
warning similar to the others and returns false so timeouts fall back to
UploadWithPresignedUrlAsync consistently with the existing transient-timeout
handling used by UploadWithPresignedUrlAsync.

---

Nitpick comments:
In `@Web/Resgrid.Web.Tts/Services/S3StorageService.cs`:
- Line 15: The hardcoded PresignedPutUrlExpiryMinutes constant in
S3StorageService prevents the fallback PUT presigned URL from using the
configured lifetime; replace uses of PresignedPutUrlExpiryMinutes with the
configured value (e.g., S3StorageOptions.PresignedUrlExpiryMinutes) accessed
from the service's injected options instance (the same one used by
GetObjectUrlAsync) inside S3StorageService (remove/stop referencing the
PresignedPutUrlExpiryMinutes constant and use the injected
_options.PresignedUrlExpiryMinutes or equivalent) so both the normal and
fallback PUT URL generation honor the deployment setting; apply this change to
all occurrences (including the block around the GetObjectUrlAsync fallback at
lines ~219-228).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ea2e768-4be4-40c6-9ce1-a5f7082087cc

📥 Commits

Reviewing files that changed from the base of the PR and between ebd1b6e and 4600e0f.

📒 Files selected for processing (3)
  • Tests/Resgrid.Tests/Web/Tts/S3StorageServiceTests.cs
  • Web/Resgrid.Web.Tts/Program.cs
  • Web/Resgrid.Web.Tts/Services/S3StorageService.cs

Comment thread Web/Resgrid.Web.Tts/Services/S3StorageService.cs
@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented May 2, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 0214160 into master May 2, 2026
18 of 19 checks passed
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.

1 participant