Skip to content

feat: add native S3 upload handler and receiver - #192

Merged
hamzahalq merged 2 commits into
releases/r8.0from
hamza/feature/native-s3-adapters
Jul 8, 2026
Merged

feat: add native S3 upload handler and receiver#192
hamzahalq merged 2 commits into
releases/r8.0from
hamza/feature/native-s3-adapters

Conversation

@hamzahalq

Copy link
Copy Markdown
Contributor

Port the serverless S3 handler and receiver into in-process native adapters (NativeS3UploadHandler, NativeS3Receiver), using the same SimplyWorks.CloudFiles.S3 library already referenced by Bitween-api.

  • Receiver's DeleteFile now always removes the fetched object; the original only deleted when DeleteMovesFileTo was set, so files were reprocessed on every poll cycle by default
  • DeleteMovesFileTo is reinterpreted as a copy to another key/prefix in the same bucket; the original wrote to local disk, which has no meaning for an in-process API pod
  • Receiver's ContentType field renamed to ResponseEncoding (utf8/base64) to match the FTP/Pop3 native adapters, since it was actually selecting response encoding, not a MIME content type
  • Fields renamed for clarity (Url -> ServiceUrl, TargetPath -> BucketName) to match the underlying CloudFilesOptions shape

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

What changed:

  • Added new in-process S3 adapters:
    • NativeS3UploadHandler (INativeInfolinkHandler) to upload XchangeFile.Data to S3 via SimplyWorks.CloudFiles.S3 (CloudFilesService).
    • NativeS3Receiver (INativeInfolinkReceiver, IDisposable) to list keys, fetch objects, and delete/copy keys via the same library.
  • Introduced adapter-specific startup input models:
    • S3UploadHandlerInput (credentials + bucket/service settings, optional folder/file naming, ContentType for write).
    • S3ReceiverInput (credentials + bucket/service settings, optional FolderName, BatchSize, and ResponseEncoding).
  • Updated receiver field semantics to align with the underlying options:
    • UrlServiceUrl
    • TargetPathBucketName
    • ContentTypeResponseEncoding with supported values utf8 / base64 (invalid values throw ArgumentException).
  • Updated receiver deletion behavior:
    • DeleteFile always removes the fetched object by default.
    • DeleteMovesFileTo is now treated as an in-bucket destination: the receiver server-side copies the object to a new key/prefix and then deletes the source (replacing prior local-disk “move” semantics).

Risk: medium

Security-sensitive areas touched:

  • S3 credentials are consumed directly by the adapter (AccessKeyId, SecretAccessKey), including new DI startup models and secure-secret annotations.
  • S3 object lifecycle operations (read, list, copy, delete) now occur in-process within the API pod.
  • Response handling depends on ResponseEncoding (utf8/base64), including throwing on unsupported values.

Test coverage impact:

  • No explicit test additions were indicated.
  • Behavior changes likely need targeted coverage for: delete/copy semantics, key computation (folder-relative destination), ResponseEncoding handling, and batch listing behavior.

Deployment, migration, rollback, operational concerns:

  • Requires runtime dependency SimplyWorks.CloudFiles.S3 (8.1.1 added to SW.Bitween.NativeAdapters).
  • Configuration migration/mapping required for renamed fields and receiver semantics (Url/TargetPath/ContentTypeServiceUrl/BucketName/ResponseEncoding).
  • Operational impact: receiver deletion is destructive by default; incorrect configuration can lead to reprocessing changes or reduced ability to re-poll previously fetched objects.
  • Rollback would require reverting to the previous adapter implementation and restoring prior config name/semantic expectations (especially receiver delete/move behavior and encoding field).

Walkthrough

Adds two new native adapters for S3-backed receive and upload flows, plus their startup input models, package dependency, and dependency-injection registration.

Changes

S3 Native Adapters

Layer / File(s) Summary
S3 receiver implementation and input contract
SW.Bitween.NativeAdapters/S3Receiver/S3ReceiverInput.cs, SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs
S3ReceiverInput defines required credentials, bucket, optional folder/delete-move settings, batch size, and response encoding; NativeS3Receiver implements list/get/delete file operations against S3 with base64/utf8 decoding and optional move-then-delete.
S3 upload handler implementation and input contract
SW.Bitween.NativeAdapters/S3UploadHandler/S3UploadHandlerInput.cs, SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs
S3UploadHandlerInput defines required credentials/bucket and optional naming/content-type fields; NativeS3UploadHandler.Handle writes file data to S3 using a computed or timestamp-based key and returns the resulting reference.
Dependency and DI registration
SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj, SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs
Adds SimplyWorks.CloudFiles.S3 package reference and registers both adapters as scoped INativeInfolinkReceiver/INativeInfolinkHandler/INativeAdapter services.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • simplify9/Bitween-api#123: Adds the API-side native adapter discovery/execution paths that enable these S3 adapters to be instantiated and run.
  • simplify9/Bitween-api#181: Adds the receiving job path that exercises INativeInfolinkReceiver lifecycle methods such as Initialize, ListFiles, GetFile, and DeleteFile.

Suggested labels: security, infra, risk:high

Suggested reviewers: AhmadRAbuhussein

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding native S3 upload and receiver adapters.
Description check ✅ Passed The description is directly related to the S3 adapter changes and their behavior updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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.

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs`:
- Around line 58-72: The delete-move logic in NativeS3Receiver.DeleteFile is
flattening nested keys by using Path.GetFileName(fileId), which can cause
collisions when ListAsync returns subdirectory paths. Update DeleteFile to
preserve the relative path from FolderName when building the targetKey, and make
sure the moved object keeps its nested directory structure under
_options.DeleteMovesFileTo rather than collapsing everything to the base
filename.
- Around line 49-55: `NativeS3Receiver` is calling `ToLower()` on
`ResponseEncoding` without handling null, which can crash when
`settings.ConvertTo<S3ReceiverInput>()` populates a null value. Update the logic
around the `ResponseEncoding` switch to guard against null before normalizing
the string, and make the `Load`/receiver path in `NativeS3Receiver` fail with a
clear `ArgumentException` if `ResponseEncoding` is missing or invalid rather
than dereferencing it.
- Around line 25-29: `NativeS3Receiver` currently only disposes `_cloudFiles`
inside `Finalize()`, so exceptions in `ListFiles`, `GetFile`, or `DeleteFile`
can leave the S3 client undisposed. Update `NativeS3Receiver` to implement
`IDisposable` and move the cleanup into a proper `Dispose` path (keeping
`Finalize()` delegating to it if needed), so the scoped receiver always releases
`_cloudFiles` regardless of success or failure. Use the `NativeS3Receiver` class
and its `Finalize()` method as the main touchpoints.
- Around line 62-68: The move logic in NativeS3Receiver currently buffers the
entire object into a MemoryStream before re-uploading it, which can exhaust
memory on large files. Update the path around _cloudFiles.OpenReadAsync and
_cloudFiles.WriteAsync to use a server-side copy or another streaming-friendly
adapter method if available, and avoid CopyToAsync into an in-memory buffer.
Keep the target key construction with Path.GetFileName(fileId) and
_options.DeleteMovesFileTo, but change the transfer approach so the file is
moved without fully loading it into memory.

In `@SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs`:
- Around line 20-27: The S3 object key generation in NativeS3UploadHandler
currently uses a second-precision timestamp when _options.FileName is empty,
which can produce duplicate keys and overwrites for rapid uploads. Update the
key creation logic in the upload path to include higher-resolution uniqueness,
such as milliseconds and a unique suffix, while preserving the existing
_options.FolderName prefix behavior. Keep the fix localized to the key-building
block that uses DateTime.UtcNow and _options.FileExtension so each upload gets a
distinct S3 key.
- Line 23: The key generation in NativeS3UploadHandler uses
_options.FileExtension directly, which can produce malformed S3 object keys when
the extension includes a leading dot or is empty. Normalize FileExtension before
building the key by stripping any leading dot and handling null/empty so the
generated value in the key assignment always has a valid format without double
dots or trailing dots.
🪄 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: Repository: simplify9/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6596375b-6ea3-4418-85a3-2754f3043896

📥 Commits

Reviewing files that changed from the base of the PR and between 2a51d29 and 5dbd0cd.

📒 Files selected for processing (6)
  • SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs
  • SW.Bitween.NativeAdapters/S3Receiver/S3ReceiverInput.cs
  • SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs
  • SW.Bitween.NativeAdapters/S3UploadHandler/S3UploadHandlerInput.cs
  • SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj
  • SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs
📜 Review details
🔇 Additional comments (4)
SW.Bitween.NativeAdapters/S3Receiver/S3ReceiverInput.cs (1)

6-30: LGTM!

SW.Bitween.NativeAdapters/S3UploadHandler/S3UploadHandlerInput.cs (1)

6-29: LGTM!

SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj (1)

25-25: LGTM!

SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs (1)

7-8: LGTM!

Also applies to: 43-48

Comment thread SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs
Comment thread SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs Outdated
Comment thread SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs
Comment thread SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs Outdated
Comment thread SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs
Comment thread SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs Outdated
hamzahalq added 2 commits July 8, 2026 11:57
Port the serverless S3 handler and receiver into in-process native
adapters (NativeS3UploadHandler, NativeS3Receiver), using the same
SimplyWorks.CloudFiles.S3 library already referenced by Bitween-api.

- Receiver's DeleteFile now always removes the fetched object; the
  original only deleted when DeleteMovesFileTo was set, so files were
  reprocessed on every poll cycle by default
- DeleteMovesFileTo is reinterpreted as a copy to another key/prefix
  in the same bucket; the original wrote to local disk, which has no
  meaning for an in-process API pod
- Receiver's ContentType field renamed to ResponseEncoding
  (utf8/base64) to match the FTP/Pop3 native adapters, since it was
  actually selecting response encoding, not a MIME content type
- Fields renamed for clarity (Url -> ServiceUrl, TargetPath ->
  BucketName) to match the underlying CloudFilesOptions shape
Guard against null encoding, use S3 server-side copy instead of
buffering in memory, avoid filename collisions on move and upload,
and dispose the S3 client reliably via IDisposable.
@hamzahalq
hamzahalq force-pushed the hamza/feature/native-s3-adapters branch from 5dbd0cd to b88b006 Compare July 8, 2026 09:53

@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: 1

♻️ Duplicate comments (1)
SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs (1)

72-89: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Trailing slash in FolderName breaks relative-path calculation in DeleteFile.

If FolderName is configured with a trailing slash (e.g., "input/"), the StartsWith(_options.FolderName + "/") check becomes StartsWith("input//"), which never matches. The relativePath falls back to the full fileId, so the moved object lands at DeleteMovesFileTo/input/file.txt instead of DeleteMovesFileTo/file.txt.

🛡️ Proposed fix: trim trailing slashes before comparison
 public async Task DeleteFile(string fileId)
 {
     if (!string.IsNullOrWhiteSpace(_options.DeleteMovesFileTo))
     {
+        var folderName = _options.FolderName?.TrimEnd('/');
         // Preserve the path relative to FolderName so files with the same name in
         // different subdirectories don't collide at the destination.
-        var relativePath = !string.IsNullOrEmpty(_options.FolderName) && fileId.StartsWith(_options.FolderName + "/")
-            ? fileId[(_options.FolderName.Length + 1)..]
+        var relativePath = !string.IsNullOrEmpty(folderName) && fileId.StartsWith(folderName + "/")
+            ? fileId[(folderName.Length + 1)..]
             : fileId;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs` around lines 72 -
89, In NativeS3Receiver.DeleteFile, the relative-path logic fails when
_options.FolderName includes a trailing slash because the StartsWith check is
built against FolderName + "/". Normalize FolderName by trimming trailing
slashes before comparing and slicing fileId, so the relative path is computed
correctly regardless of whether the configured folder ends with "/". Keep the
fix localized to DeleteFile and use the existing _options.FolderName, fileId,
and relativePath flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj`:
- Line 25: The package reference for SimplyWorks.CloudFiles.S3 is outdated and
should be updated to the requested patch release. Change the Version on the
PackageReference in the SW.Bitween.NativeAdapters project file from the current
8.1.1 to 8.1.6, keeping the package name unchanged and ensuring the project
still restores cleanly after the version bump.

---

Duplicate comments:
In `@SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs`:
- Around line 72-89: In NativeS3Receiver.DeleteFile, the relative-path logic
fails when _options.FolderName includes a trailing slash because the StartsWith
check is built against FolderName + "/". Normalize FolderName by trimming
trailing slashes before comparing and slicing fileId, so the relative path is
computed correctly regardless of whether the configured folder ends with "/".
Keep the fix localized to DeleteFile and use the existing _options.FolderName,
fileId, and relativePath flow.
🪄 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: Repository: simplify9/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 52683d03-d044-471e-91b5-19b8a1bc08d1

📥 Commits

Reviewing files that changed from the base of the PR and between 5dbd0cd and b88b006.

📒 Files selected for processing (6)
  • SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs
  • SW.Bitween.NativeAdapters/S3Receiver/S3ReceiverInput.cs
  • SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs
  • SW.Bitween.NativeAdapters/S3UploadHandler/S3UploadHandlerInput.cs
  • SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj
  • SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs
📜 Review details
🔇 Additional comments (5)
SW.Bitween.NativeAdapters/S3Receiver/S3ReceiverInput.cs (1)

1-30: LGTM!

SW.Bitween.NativeAdapters/S3Receiver/NativeS3Receiver.cs (1)

1-70: LGTM! Past review issues (IDisposable, ResponseEncoding null guard, Path.GetFileName collision, in-memory buffering for move) have been resolved.

Also applies to: 91-99

SW.Bitween.NativeAdapters/S3UploadHandler/S3UploadHandlerInput.cs (1)

1-29: LGTM!

SW.Bitween.NativeAdapters/S3UploadHandler/NativeS3UploadHandler.cs (1)

1-48: LGTM! Past review issues (second-precision timestamp collisions, FileExtension leading-dot normalization) have been resolved with GUID suffix and TrimStart('.').

SW.Bitween.NativeAdapters/ServiceCollectionExtensions.cs (1)

7-8: LGTM! Scoped registrations follow the existing adapter pattern and feed NativeAdapterDiscoveryService correctly.

Also applies to: 43-47

<PackageReference Include="Rebex.Pop3" Version="8.0.9673" />
<PackageReference Include="Rebex.Sftp" Version="8.0.9673" />
<PackageReference Include="Scriban" Version="7.0.6" />
<PackageReference Include="SimplyWorks.CloudFiles.S3" Version="8.1.1" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check NuGet for the latest version and any security advisories.
curl -s "https://api.nuget.org/v3-flatcontainer/simplyworks.cloudfiles.s3/index.json" | jq '.versions[-3:]'
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NUGET, package: "SimplyWorks.CloudFiles.S3") {
    nodes {
      advisory { summary severity publishedAt }
      vulnerableVersionRange
      firstPatchedVersion { identifier }
    }
  }
}'

Repository: simplify9/Bitween-api

Length of output: 244


Upgrade SimplyWorks.CloudFiles.S3 to 8.1.6 8.1.1 is several patch releases behind the latest available release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@SW.Bitween.NativeAdapters/SW.Bitween.NativeAdapters.csproj` at line 25, The
package reference for SimplyWorks.CloudFiles.S3 is outdated and should be
updated to the requested patch release. Change the Version on the
PackageReference in the SW.Bitween.NativeAdapters project file from the current
8.1.1 to 8.1.6, keeping the package name unchanged and ensuring the project
still restores cleanly after the version bump.

@hamzahalq
hamzahalq merged commit b0fc0c9 into releases/r8.0 Jul 8, 2026
2 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.

2 participants