Skip to content

Muhannad/api gateway - #124

Merged
AhmadRAbuhussein merged 10 commits into
releases/r8.0from
muhannad/api-gateway
Feb 25, 2026
Merged

Muhannad/api gateway#124
AhmadRAbuhussein merged 10 commits into
releases/r8.0from
muhannad/api-gateway

Conversation

@mmalkhatib

@mmalkhatib mmalkhatib commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • API Gateway management: create, update, delete, search, and manage partner associations.
    • New gateway endpoints to submit exchanges synchronously or asynchronously with optional result polling.
    • Global Adapter Values: CRUD for shared adapter configuration sets and runtime resolution.
    • Native adapter support (discovery, local execution) and a new HTTP native adapter for direct integrations.
    • Subscription enhancements: new Gateway API Call type and updated validation/flows.

@gitguardian

gitguardian Bot commented Feb 17, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 8 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
20562020 Triggered Generic Password 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
23522284 Triggered DigitalOcean Spaces Keys 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
23846048 Triggered ODBC Connection String 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
9611220 Triggered AMQP Credentials 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
27115147 Triggered DigitalOcean Spaces Keys 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
27115146 Triggered DigitalOcean Spaces Keys 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
69690 Triggered DigitalOcean Spaces Keys 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
27115145 Triggered AMQP Credentials 9b98028 SW.Bitween.Web/Properties/launchSettings.json View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@coderabbitai

coderabbitai Bot commented Feb 17, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

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

⌛ 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.

📥 Commits

Reviewing files that changed from the base of the PR and between a93be33 and 2eaaf20.

📒 Files selected for processing (17)
  • SW.Bitween.Api/Controllers/GatewayController.cs
  • SW.Bitween.Api/Domain/Xchange/Xchange.cs
  • SW.Bitween.Api/Extensions/InfolinkDbContextExtensions.cs
  • SW.Bitween.Api/Helpers/StartupValuesFiller.cs
  • SW.Bitween.Api/Resources/ApiGateways/AddPartner.cs
  • SW.Bitween.Api/Resources/ApiGateways/Get.cs
  • SW.Bitween.Api/Resources/ApiGateways/RemovePartner.cs
  • SW.Bitween.Api/Resources/ApiGateways/UpdatePartner.cs
  • SW.Bitween.Api/Resources/Partners/Get.cs
  • SW.Bitween.Api/Resources/Partners/Update.cs
  • SW.Bitween.Api/Resources/Subscriptions/Create.cs
  • SW.Bitween.Api/Resources/Subscriptions/Update.cs
  • SW.Bitween.Api/Services/XchangeService.cs
  • SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs
  • SW.Bitween.Sdk/Model/Partner.cs
  • SW.Bitween.Web/Properties/launchSettings.json
  • SW.Bitween.Web/Startup.cs
📝 Walkthrough

Walkthrough

Adds API Gateway support: new domain entities and SDK models, DB migrations across providers, controller endpoints for sync/async gateway calls with partner authorization and Fibonacci backoff polling, global adapter values and template resolution, native-adapter discovery and runtime path (native vs serverless), plus a new NativeAdapters project (HTTP handler and helpers).

Changes

Cohort / File(s) Summary
API Gateway Controller & Resources
SW.Bitween.Api/Controllers/GatewayController.cs, SW.Bitween.Api/Resources/ApiGateways/*
New GatewayController with POST sync/async endpoints and resource handlers for Create/Get/Search/Update/Delete and partner management (Add/Update/Remove). Includes partner authorization, xchange submission, and Fibonacci backoff polling for sync results.
Domain Models & SDK
SW.Bitween.Api/Domain/Gateway/..., SW.Bitween.Api/Domain/GlobalAdapterValue/..., SW.Bitween.Sdk/Model/*, SW.Bitween.Api/Domain/*
Adds ApiGateway, ApiGatewayPartner, GlobalAdapterValuesSet; extends Partner and Subscription (GatewayApiCall) and updates Xchange constructors/mapper to carry gateway/global context. SDK DTOs updated accordingly.
Global Adapter Values Handlers
SW.Bitween.Api/Resources/GlobalAdapterValuesSets/*
CRUD handlers (Create/Get/Search/Update/Delete) with validation and access control for GlobalAdapterValuesSet.
EF Core & Migrations
SW.Bitween.Api/Data/BitweenDbContext.cs, SW.Bitween.MsSql/..., SW.Bitween.MySql/..., SW.Bitween.PgSql/...
Model mappings for ApiGateway, ApiGatewayPartner, GlobalAdapterValuesSet; adds AdapterProperties to Partner; adds migrations and snapshots for MS SQL, MySQL, and Postgres.
Authorization Extensions
SW.Bitween.Api/Extensions/InfolinkDbContextExtensions.cs
New partner authorization helpers returning tuples: CheckPartnerAuthorized and AuthorizePartner (non-throwing check + throwing helper).
Service Layer & Caching
SW.Bitween.Api/Services/XchangeService.cs, SW.Bitween.Api/Services/ReceivingService.cs, SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs, SW.Bitween.Api/Services/NativeAdapterDiscoveryService.cs
Introduces native-adapter discovery and runtime paths; XchangeService and ReceivingService dispatch to native adapters when applicable and accept gateway/global parameters; cache extended to store/list GlobalAdapterValuesSet.
Startup Values & Adapter Discovery
SW.Bitween.Api/Helpers/StartupValuesFiller.cs, SW.Bitween.Api/Resources/Adapters/*, SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs
Template resolution for startup values (global + partner); adapter search/get properties now integrate native adapters via NativeAdapterDiscoveryService; JSON dictionary converter added for native adapters.
Native Adapters Project
SW.Bitween.NativeAdapters/*
New project with HttpHandler (supports ApiKey/Bearer/Basic/Login/OAuth2, request templating), DTOs, DictionaryConverter, and discovery service.
Subscription Validation & Flows
SW.Bitween.Api/Resources/Subscriptions/*
Adds SubscriptionType.GatewayApiCall handling and enhanced Update validation to branch native vs serverless for required properties; Receiving/Aggregation/GatewayApiCall-specific validations.
Partner API & DTOs
SW.Bitween.Api/Resources/Partners/*, SW.Bitween.Sdk/Model/Partner.cs
Exposes AdapterProperties on Partner projections and updates to persist AdapterProperties on update.
Project & Startup Changes
SW.Bitween.Api/*.csproj, SW.Bitween.Web/Startup.cs, SW.Bitween.Web/*.csproj, SW.Bitween.sln
Adds NativeAdapters project reference, registers NativeAdapterDiscoveryService and HttpContextAccessor, adds migration config file.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant GatewayController
participant DB as BitweenDbContext
participant RequestCtx as RequestContext
participant Xchange as XchangeService
participant Cache as InfolinkCache

Client->>GatewayController: POST /api/gateway/{name}/sync or /async + body + partner key
GatewayController->>DB: Load ApiGateway by UrlName
GatewayController->>RequestCtx: Resolve partner key
RequestCtx->>DB: (via extensions) Find Partner
GatewayController->>DB: Load Subscription for partner
GatewayController->>Cache: ListGlobalAdapterValuesSetsAsync()
GatewayController->>Xchange: SubmitSubscriptionXchange(..., gatewayPartner, globalAdapterValuesSets)
alt resultSync = false
    GatewayController->>Client: 202 Accepted (xchangeId)
else resultSync = true
    GatewayController->>Xchange: Poll for XchangeResult (backoff loop)
    Xchange-->>GatewayController: XchangeResult (Success/Failure/ResponseFileId)
    alt Success with ResponseFileId
        GatewayController->>Xchange: GetFile(responseFileId)
        Xchange-->>GatewayController: File content
        GatewayController->>Client: 200 OK (content) or 400 (if ResponseBad)
    else Success without response
        GatewayController->>Client: 200 OK (xchangeId)
    else Failure
        GatewayController->>Client: 400 Bad Request
    end
end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Suggested reviewers

  • samerzughul

Poem

🐰 A gateway springs up with keys and queues,
Native handlers hum while templates peruse,
Partners handshake, globals fill the maps,
Xchanges wait through Fibonacci claps,
A rabbit hops on code — hooray for new apps!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.30% 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 'Muhannad/api gateway' is vague and generic, using a developer name and general feature area without clearly describing the primary changes in the changeset. Use a more descriptive title that clarifies the main purpose, such as 'Add API gateway integration with native adapters' or 'Implement gateway-based xchange processing with global adapter values'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch muhannad/api-gateway

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.

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

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
SW.Bitween.Web/appsettings.json (1)

4-45: ⚠️ Potential issue | 🔴 Critical

🚨 Real secrets and credentials committed to source control.

This file contains plaintext credentials for multiple external cloud services (DigitalOcean DB, CloudAMQP RabbitMQ, DigitalOcean Spaces), a token signing key, and trivially weak admin credentials (1:1). These are not localhost-only placeholders — they point to live cloud infrastructure and will be part of the repository's Git history permanently once merged.

Immediate actions required:

  1. Rotate all exposed credentials now — they must be considered compromised once pushed to any remote.
  2. Move all secrets to a secure store (environment variables, .NET User Secrets for local dev, or a vault like Azure Key Vault / HashiCorp Vault for deployed environments).
  3. Replace values in appsettings.json with non-sensitive placeholders or remove the keys entirely.
  4. Ensure appsettings.Development.json (with real dev values) is in .gitignore.

Static analysis also flagged this: Gitleaks detected generic API keys on Lines 7 and 30; Checkov detected basic auth credentials on Lines 8–9.

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

In `@SW.Bitween.Web/appsettings.json` around lines 4 - 45, This file contains real
secrets in keys like ConnectionStrings (InfolinkDb, RabbitMQ,
BitweenDb_Postgresql), CloudFiles (AccessKeyId, SecretAccessKey, ServiceUrl),
Token:Key, and Bitween:AdminCredentials; remove these plaintext secrets from
appsettings.json and replace with non-sensitive placeholders (e.g., "<REDACTED>"
or env var names), then wire the runtime to read the real values from a secure
source (environment variables, .NET User Secrets for local dev, or a secrets
vault) and ensure any development override file (e.g.,
appsettings.Development.json) that may contain secrets is added to .gitignore;
finally, treat the exposed credentials as compromised and rotate them
immediately.
SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs (1)

169-175: ⚠️ Potential issue | 🔴 Critical

Revoke() does not clear the GlobalAdapterValuesSet cache entry.

GlobalAdapterValuesSet is loaded and cached in Load() (line 42/49), but Revoke() only removes Subscription, Notifier, Document, and WorkGroup. After a CRUD operation on GlobalAdapterValuesSet, calling Revoke() will leave stale data in the cache until the 10-minute TTL expires.

Proposed fix
     public void Revoke()
     {
         _cache.Remove(nameof(Subscription));
         _cache.Remove(nameof(Notifier));
         _cache.Remove(nameof(Document));
         _cache.Remove(nameof(WorkGroup));
+        _cache.Remove(nameof(GlobalAdapterValuesSet));
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Services/Caching/InMemoryInfolinkCache.cs` around lines 169 -
175, Revoke() currently removes Subscription, Notifier, Document, and WorkGroup
but misses the GlobalAdapterValuesSet entry cached by Load(); update the
Revoke() method to also remove the GlobalAdapterValuesSet key (call
_cache.Remove(nameof(GlobalAdapterValuesSet))) so the cache is invalidated after
CRUD ops, and verify the key name matches how Load() stores it
(nameof(GlobalAdapterValuesSet) or the exact string constant) to avoid stale
entries.
SW.Bitween.Api/Resources/Subscriptions/Update.cs (1)

103-195: ⚠️ Potential issue | 🟠 Major

Avoid null reference when property collections are omitted.
MapperProperties, HandlerProperties, and ReceiverProperties can be null in update payloads; the current .Where(...) will throw instead of producing validation errors.

🛠️ Suggested fix
-                        var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase)
-                            .Except(i.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key));
+                        var provided = (i ?? Enumerable.Empty<KeyAndValue>())
+                            .Where(p => !string.IsNullOrEmpty(p.Value))
+                            .Select(p => p.Key);
+                        var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase)
+                            .Except(provided);
-                        var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase)
-                            .Except(i.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key));
+                        var provided = (i ?? Enumerable.Empty<KeyAndValue>())
+                            .Where(p => !string.IsNullOrEmpty(p.Value))
+                            .Select(p => p.Key);
+                        var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase)
+                            .Except(provided);
-                            var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase)
-                                .Except(model.ReceiverProperties.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p => p.Key));
+                            var provided = (model.ReceiverProperties ?? Enumerable.Empty<KeyAndValue>())
+                                .Where(p => !string.IsNullOrEmpty(p.Value))
+                                .Select(p => p.Key);
+                            var missing = mustProps.ToHashSet(StringComparer.OrdinalIgnoreCase)
+                                .Except(provided);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/Subscriptions/Update.cs` around lines 103 - 195, The
validation assumes MapperProperties, HandlerProperties and ReceiverProperties
are non-null and calls .Where(...) which can throw; update the three CustomAsync
blocks (the RuleFor(i => i.MapperProperties) handler, RuleFor(i =>
i.HandlerProperties) handler, and the ReceiverProperties check inside RuleFor(i
=> i).CustomAsync) to treat null collections as empty before filtering—e.g.
replace uses like i.Where(p => !string.IsNullOrEmpty(p.Value)).Select(p =>
p.Key) with (i ??
Enumerable.Empty<KeyValuePair<string,string>>()).Where(...).Select(...) or the
correct empty sequence type for the property, so missing/null property
collections produce no exception and validation still reports missing required
keys.
🟠 Major comments (16)
SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs-112-114 (1)

112-114: ⚠️ Potential issue | 🟠 Major

Null values are silently dropped from arrays — data loss.

When deserializing [1, null, 3], the null element is discarded, producing [1, 3]. This changes the semantics of the data (e.g., positional arrays, sparse data). The same issue exists in ReadObject (line 140-141) where {"key": null} loses the entry entirely.

🐛 Proposed fix for both ReadArray and ReadObject

In ReadArray:

                 default:
                     var v = ReadValue(reader);
-                    if (v != null)
-                        list.Add(v);
+                    list.Add(v);
                     break;

In ReadObject:

                     var v = ReadValue(reader);
-                    if (v != null)
-                        obj[propertyName] = v;
+                    obj[propertyName] = v;
                     break;

Note: obj would need to be Dictionary<string, object?> (or the class would need to use IDictionary<string, object?>) to store nulls properly. Consider whether the dictionary value type should be object? throughout.

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

In `@SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs` around lines 112 -
114, ReadArray and ReadObject currently drop nulls because they only add values
when ReadValue(reader) != null; change both to always add the result even if
null (i.e., call list.Add(v) unconditionally in ReadArray and assign obj[key] =
v in ReadObject), and update the container types to allow nulls by using
Dictionary<string, object?> (or IDictionary<string, object?>) for objects and
List<object?> for arrays so null elements and null-valued keys are preserved
throughout deserialization.
SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs-66-80 (1)

66-80: ⚠️ Potential issue | 🟠 Major

OAuth2 flow lacks error handling on the token response.

If the OAuth2 token endpoint returns an error (non-2xx), resDeserialized?.access_token will silently be null, and subsequent API calls will fail with an opaque auth error. Add status validation similar to the Login flow.

Proposed fix
             var oauthResponse = await client.SendAsync(oathRequest);
+            oauthResponse.EnsureSuccessStatusCode();
             var res = await oauthResponse.Content.ReadAsStringAsync();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs` around lines 66 - 80,
The OAuth2 branch in HttpHandler (when _options.AuthType == "OAuth2") doesn't
validate the token endpoint response; update the block around
oathRequest/oauthResponse/resDeserialized to verify
oauthResponse.IsSuccessStatusCode (like the Login flow), and if not successful
read the response, log or throw a descriptive exception including the status
code and response body; also guard against null resDeserialized or null
resDeserialized.access_token and handle that case by logging/throwing before
assigning client.DefaultRequestHeaders.Authorization (reference OAuth2Response,
oauthResponse, resDeserialized, and client.DefaultRequestHeaders.Authorization).
SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs-36-36 (1)

36-36: ⚠️ Potential issue | 🟠 Major

HttpClient is created per call and never disposed — risk of socket exhaustion.

HttpClient is designed to be long-lived and reused. Creating a new instance per Handle call leaks sockets and can exhaust the connection pool under load. At minimum, wrap it in a using statement; ideally, inject IHttpClientFactory or a shared HttpClient instance.

Minimal fix: dispose after use
-        HttpClient client = new HttpClient();
+        using HttpClient client = new HttpClient();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs` at line 36, The
HttpClient instance is created per call in HttpHandler (line with "HttpClient
client = new HttpClient()") and never disposed, risking socket exhaustion;
update the HttpHandler.Handle (or the class constructor) to reuse a single
HttpClient or accept an injected IHttpClientFactory/IHttpClient to create
clients, or at minimum wrap the per-call HttpClient in a using/Dispose; prefer
constructor-injected IHttpClientFactory or a shared readonly HttpClient field on
HttpHandler to ensure long-lived reuse and remove the per-call new HttpClient()
allocation.
SW.Bitween.Web/appsettings.Migration.json-1-8 (1)

1-8: ⚠️ Potential issue | 🟠 Major

Hardcoded credentials committed to source control.

This file contains a plaintext password (root/password) for the migration database. Even if intended only for local development, committing credentials sets a bad precedent and risks accidental use in non-local environments. Consider adding this file to .gitignore and using environment variables or user-secrets instead, or at minimum document clearly that this is a local-only template.

#!/bin/bash
# Check if appsettings.Migration.json is in .gitignore
echo "=== .gitignore check ==="
rg -n "appsettings.Migration" .gitignore 2>/dev/null || echo "Not found in .gitignore"

# Check if there are other appsettings files with credentials
echo "=== Other appsettings with passwords ==="
rg -n "Password=" --glob "appsettings*"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Web/appsettings.Migration.json` around lines 1 - 8, Remove the
hardcoded DB credentials from appsettings.Migration.json by replacing the
"ConnectionStrings:BitweenDb" value with a placeholder or reference to
environment variables (e.g., use configuration keys rather than plaintext), add
appsettings.Migration.json to .gitignore so it isn’t committed, create a
checked-in template (e.g., appsettings.Migration.template.json) showing the
required keys ("ConnectionStrings"/"BitweenDb" and "Bitween:DatabaseType") and
document in README or comments that developers must set real credentials via
environment variables or user-secrets for local development only.
SW.Bitween.Api/Domain/GlobalAdapterValue/GlobalAdapterValuesSet.cs-7-11 (1)

7-11: ⚠️ Potential issue | 🟠 Major

Values can be null at runtime, risking NullReferenceException.

The Values property has no default initializer. In StartupValuesFiller.cs (line ~57), globalSet.Values.FirstOrDefault(...) is called without a null check, which will throw if Values is null. With nullable enabled in the project, the compiler should also warn about uninitialized non-nullable properties.

🛡️ Proposed fix — initialize properties
 public class GlobalAdapterValuesSet:BaseEntity<string>
 {
-    public string Name { get; set; }
-    public Dictionary<string, string> Values { get; set; }
+    public string Name { get; set; } = string.Empty;
+    public Dictionary<string, string> Values { get; set; } = new();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Domain/GlobalAdapterValue/GlobalAdapterValuesSet.cs` around
lines 7 - 11, The Values property on GlobalAdapterValuesSet can be null and is
later accessed without a null check; update the GlobalAdapterValuesSet class to
initialize Values with an empty Dictionary<string,string> (e.g., default = new
Dictionary<string,string>()) so callers like the code that calls
globalSet.Values.FirstOrDefault(...) cannot hit a NullReferenceException;
alternatively, make Values nullable (Dictionary<string,string>?) and add null
checks where used (e.g., in StartupValuesFiller usage) — prefer initializing the
property to a non-null empty dictionary to preserve callers and satisfy nullable
warnings.
SW.Bitween.Api/Interfaces/IInfolinkCache.cs-23-24 (1)

23-24: 🛠️ Refactor suggestion | 🟠 Major

Rename GlobalAdapterValuesSetById to GlobalAdapterValuesSetByIdAsync to match the naming convention of other Task-returning methods in the interface.

Every other method returning Task uses the Async suffix (e.g., DocumentByIdAsync, WorkGroupByIdAsync, ListGlobalAdapterValuesSetsAsync). This method should follow the same pattern.

♻️ Changes required
  • IInfolinkCache.cs line 23
  • InMemoryInfolinkCache.cs line 147
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Interfaces/IInfolinkCache.cs` around lines 23 - 24, Rename the
method GlobalAdapterValuesSetById to GlobalAdapterValuesSetByIdAsync in the
IInfolinkCache interface and update the implementation InMemoryInfolinkCache
(and any other implementers/call sites) to match the new name and signature;
ensure Task<GlobalAdapterValuesSet> GlobalAdapterValuesSetByIdAsync(string
globalAdapterValuesSetId) replaces the old declaration, the
InMemoryInfolinkCache method named GlobalAdapterValuesSetById is renamed to
GlobalAdapterValuesSetByIdAsync, and update all references/usages to call the
Async-named method so the interface and implementation remain consistent.
SW.Bitween.Sdk/Model/GlobalAdapterValuesSet.cs-6-16 (1)

6-16: ⚠️ Potential issue | 🟠 Major

Id property hiding: GlobalAdapterValuesSetRow.Id hides GlobalAdapterValuesSetCreate.Id.

GlobalAdapterValuesSetRow (line 15) re-declares Id which is already inherited from GlobalAdapterValuesSetCreate (line 8) through the chain Row → Update → Create. This will produce a CS0108 compiler warning and can cause subtle bugs when the object is accessed through a base-type reference (the wrong Id property would be read).

Additionally, the hierarchy is unusual — Create DTO typically shouldn't carry Id, while Update/Row should. Consider restructuring so Id lives only on the types that need it.

Proposed restructuring
-    public class GlobalAdapterValuesSetCreate : IName
-    {
-        public string Id { get; set; }
-        public string Name { get; set; }
-        public Dictionary<string, string> Values { get; set; }
-    }
-
-    public class GlobalAdapterValuesSetRow : GlobalAdapterValuesSetUpdate
-    {
-        public string Id { get; set; }
-    }
-
-    public class GlobalAdapterValuesSetUpdate : GlobalAdapterValuesSetCreate
-    {
-    }
+    public class GlobalAdapterValuesSetCreate : IName
+    {
+        public string Id { get; set; }
+        public string Name { get; set; }
+        public Dictionary<string, string> Values { get; set; }
+    }
+
+    public class GlobalAdapterValuesSetUpdate : GlobalAdapterValuesSetCreate
+    {
+    }
+
+    public class GlobalAdapterValuesSetRow : GlobalAdapterValuesSetUpdate
+    {
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Sdk/Model/GlobalAdapterValuesSet.cs` around lines 6 - 16,
GlobalAdapterValuesSetRow.Id is hiding GlobalAdapterValuesSetCreate.Id; fix by
removing Id from the DTO that should not carry it: delete the Id property from
GlobalAdapterValuesSetCreate and GlobalAdapterValuesSetRow, and declare a single
Id property on GlobalAdapterValuesSetUpdate so only update/row types have Id
(adjust constructors/serializers if any rely on the removed Id).
SW.Bitween.Api/Resources/Adapters/Search.cs-46-48 (1)

46-48: ⚠️ Potential issue | 🟠 Major

ToDictionary will throw on duplicate keys if native and external adapter names overlap.

If any key from nativeAdapters also appears in cloudFilesList, ToDictionary(k => k, v => v) throws ArgumentException. Consider using a safe merge or deduplicating the combined list:

Proposed fix
-            var allAdapters = nativeAdapters.Concat(cloudFilesList);
-
-            return allAdapters.ToDictionary(k => k, v => v);
+            var allAdapters = nativeAdapters.Concat(cloudFilesList).Distinct();
+
+            return allAdapters.ToDictionary(k => k, v => v);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/Adapters/Search.cs` around lines 46 - 48, The
current merge using nativeAdapters.Concat(cloudFilesList) then ToDictionary(k =>
k, v => v) will throw if an adapter name appears in both lists; change the merge
to deduplicate before building the dictionary, e.g. replace allAdapters =
nativeAdapters.Concat(cloudFilesList) with a deduped sequence (use .Distinct()
on the concatenated enumerable or GroupBy the keys and pick the first) and then
call ToDictionary on that deduped sequence so duplicates in nativeAdapters or
cloudFilesList (symbols: nativeAdapters, cloudFilesList, allAdapters,
ToDictionary) no longer cause ArgumentException.
SW.Bitween.Api/Resources/ApiGateways/RemovePartner.cs-32-38 (1)

32-38: ⚠️ Potential issue | 🟠 Major

RemovePartner must accept SubscriptionId to match the composite primary key design.

The ApiGatewayPartners table has a composite PK (ApiGatewayId, PartnerId, SubscriptionId). The AddPartner handler explicitly validates uniqueness using both PartnerId and SubscriptionId (line 46 of AddPartner.cs), confirming that multiple subscriptions per partner per gateway are intentional. However, RemovePartner filters by PartnerId alone and uses FirstOrDefault, which silently removes only the first match—creating an asymmetry between add and remove operations.

Add SubscriptionId to RemovePartnerRequest to enable precise removal of specific partner-subscription links:

Proposed fix
 public class RemovePartnerRequest
 {
     public int PartnerId { get; set; }
+    public int SubscriptionId { get; set; }
 }
-            var partnerLink = gateway.Partners?
-                .FirstOrDefault(p => p.PartnerId == request.PartnerId);
+            var partnerLink = gateway.Partners?
+                .FirstOrDefault(p => p.PartnerId == request.PartnerId && p.SubscriptionId == request.SubscriptionId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/ApiGateways/RemovePartner.cs` around lines 32 - 38,
RemovePartner currently identifies links by PartnerId only and uses
FirstOrDefault on gateway.Partners, which ignores SubscriptionId and can remove
the wrong row; update the RemovePartnerRequest to include SubscriptionId and
change the lookup in the RemovePartner handler (the code using gateway.Partners
and variable partnerLink) to filter by both PartnerId and SubscriptionId
(matching the composite PK in ApiGatewayPartners) before calling
_dbContext.Remove(partnerLink), and throw SWNotFoundException if no exact match
is found; ensure this mirrors the uniqueness checks in AddPartner.
SW.Bitween.Api/Services/ReceivingService.cs-98-99 (1)

98-99: ⚠️ Potential issue | 🟠 Major

GetService may return null — use GetRequiredService instead.

On line 98 (and line 122 for the external path), GetService<XchangeService>() is used, which returns null if the service is not registered, leading to a NullReferenceException on the next line. This is inconsistent with the pattern used elsewhere in the file (e.g., line 108 uses GetRequiredService).

Proposed fix
-                    var xchangeService = serviceProvider.GetService<XchangeService>();
+                    var xchangeService = serviceProvider.GetRequiredService<XchangeService>();

Apply the same fix on line 122 for the external adapter path:

-                    var xchangeService = serviceProvider.GetService<XchangeService>();
+                    var xchangeService = serviceProvider.GetRequiredService<XchangeService>();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Services/ReceivingService.cs` around lines 98 - 99, The code
calls serviceProvider.GetService<XchangeService>() which can return null and
then immediately calls SubmitSubscriptionXchange causing a potential
NullReferenceException; replace GetService<XchangeService>() with
GetRequiredService<XchangeService>() in both places (the internal path where
xchangeService is acquired and the external adapter path) so the DI system
throws a clear error if XchangeService is not registered before invoking
XchangeService.SubmitSubscriptionXchange(subId, xchangeFile).
SW.Bitween.Api/Services/ReceivingService.cs-139-147 (1)

139-147: ⚠️ Potential issue | 🟠 Major

Fragile constructor selection — relies on ordering and assumes single-parameter input model.

FirstOrDefault(c => c.GetParameters().Length > 0) picks the first constructor with any parameters, regardless of parameter count. If the adapter has multiple constructors (e.g., one taking DI services), this could select the wrong one. Additionally, only the first parameter's type is used as the input model (line 146), ignoring any additional parameters.

Consider filtering for constructors with exactly one parameter, or using a convention/attribute to identify the correct constructor.

Proposed fix — prefer single-parameter constructors
-            var constructor = adapterInfo.Type.GetConstructors()
-                .FirstOrDefault(c => c.GetParameters().Length > 0);
+            var constructor = adapterInfo.Type.GetConstructors()
+                .FirstOrDefault(c => c.GetParameters().Length == 1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Services/ReceivingService.cs` around lines 139 - 147, The
current constructor selection uses
adapterInfo.Type.GetConstructors().FirstOrDefault(c => c.GetParameters().Length
> 0) which can pick the wrong overload and then uses the first parameter as the
input model (inputParameter / inputType); change this to find a constructor with
exactly one parameter (e.g., FirstOrDefault(c => c.GetParameters().Length == 1))
and throw the existing BitweenException(adapterId) if none found; alternatively,
if you prefer explicit selection, look for a constructor marked by a custom
attribute (e.g., [NativeAdapterInput]) on adapterInfo.Type constructors and use
that constructor's sole parameter type as inputType.
SW.Bitween.Api/Services/ReceivingService.cs-162-173 (1)

162-173: ⚠️ Potential issue | 🟠 Major

Silent catch swallows all conversion errors — can mask configuration bugs.

The empty catch at line 168 swallows every exception from Convert.ChangeType, not just format mismatches. If the target property is not a string, the value is silently dropped. This makes misconfigured adapter properties very hard to diagnose.

At minimum, log a warning so operators can troubleshoot why a property wasn't set.

Proposed fix
                     catch
                     {
-                        // If conversion fails, set string value directly
-                        if (prop.PropertyType == typeof(string))
-                            prop.SetValue(inputInstance, value);
+                        if (prop.PropertyType == typeof(string))
+                        {
+                            prop.SetValue(inputInstance, value);
+                        }
+                        else
+                        {
+                            logger.LogWarning(
+                                "Failed to convert property '{Property}' value '{Value}' to type '{Type}' for adapter '{Adapter}'",
+                                prop.Name, value, prop.PropertyType.Name, adapterId);
+                        }
                     }

Note: logger is a class field, but InstantiateNativeReceiver is a private method without access to it. Either pass the logger as a parameter or make it accessible.

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

In `@SW.Bitween.Api/Services/ReceivingService.cs` around lines 162 - 173, The
empty catch in InstantiateNativeReceiver around Convert.ChangeType/prop.SetValue
swallows all exceptions and drops non-string values; change it to catch specific
exceptions (e.g., InvalidCastException, FormatException, OverflowException) and
use the class logger to log a warning including the exception and target
property name and type before falling back to the string assignment for props of
type string; for non-string properties either rethrow or log an error so the
misconfiguration is visible. Since InstantiateNativeReceiver currently lacks
access to logger, pass the logger into InstantiateNativeReceiver (or make the
logger field accessible) so you can log the warning when conversion fails, and
keep the Nullable.GetUnderlyingType logic and prop.SetValue usage intact.
SW.Bitween.Api/Helpers/StartupValuesFiller.cs-55-66 (1)

55-66: ⚠️ Potential issue | 🟠 Major

Handle null Values in GlobalAdapterValuesSet.
If a global set exists but Values is null, the current lookup will throw instead of preserving the original template.

🛠️ Suggested fix
-            return globalSet.Values.FirstOrDefault(v => 
-                v.Key.Equals(keyName, StringComparison.OrdinalIgnoreCase)).Value;
+            if (globalSet.Values == null)
+            {
+                return null;
+            }
+            return globalSet.Values.FirstOrDefault(v => 
+                v.Key.Equals(keyName, StringComparison.OrdinalIgnoreCase)).Value;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Helpers/StartupValuesFiller.cs` around lines 55 - 66, The
lookup can throw when a matching GlobalAdapterValuesSet exists but its Values
property is null; modify the lambda that finds the replacement so after locating
globalSet (via globals.FirstOrDefault(...)) you check for globalSet.Values being
null and return null (preserving the original template) before calling
FirstOrDefault on Values; update the code paths around globalSet and the keyName
lookup to guard against null Values (e.g., if (globalSet?.Values == null) return
null) so the call to v.Key.Equals(keyName, ...) is never invoked on a null
collection.
SW.Bitween.Api/Controllers/GatewayController.cs-76-82 (1)

76-82: ⚠️ Potential issue | 🟠 Major

No upper bound on Wait-Period header — a client can tie up a server thread for an arbitrarily long time.

A malicious or misconfigured client could set Wait-Period: 999999, holding a thread (and polling the DB) for days. Consider clamping to a reasonable maximum (e.g., 300 seconds).

🛡️ Proposed fix
+        const int maxWaitPeriod = 300;
         var waitResponse = 120;
         var waitResponseHeader = Request.Headers["Wait-Period"].FirstOrDefault();
         if (int.TryParse(waitResponseHeader, out var waitResponseValue))
         {
-            waitResponse = waitResponseValue <= 0 ? 120 : waitResponseValue;
+            waitResponse = waitResponseValue <= 0 ? 120 : Math.Min(waitResponseValue, maxWaitPeriod);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Controllers/GatewayController.cs` around lines 76 - 82, The
Wait-Period header handling currently allows arbitrarily large values via
waitResponse; modify the parsing logic in GatewayController (the waitResponse
variable and the Request.Headers["Wait-Period"] parsing block) to clamp the
parsed waitResponseValue to a safe maximum (e.g., 300 seconds) and a minimum >0,
preserving the default 120 when parsing fails; use Math.Clamp(waitResponseValue,
1, 300) or equivalent so clients cannot tie up server threads indefinitely.
SW.Bitween.Api/Services/XchangeService.cs-207-256 (1)

207-256: 🛠️ Refactor suggestion | 🟠 Major

Extract shared adapter instantiation logic to reduce duplication and improve robustness.

  1. Code duplication: This method duplicates the property-mapping and instantiation logic from ReceivingService.InstantiateNativeReceiver (lines 130–180). Extract the common logic into a shared helper (e.g., on NativeAdapterDiscoveryService) to maintain a single source of truth.

  2. Silent failure on type conversion: The bare catch block (lines 240–244) swallows all conversion errors without logging. For non-string properties, values are silently dropped, making runtime issues difficult to diagnose. Log or throw on unexpected conversion failures.

  3. Unsafe cast: The cast (T)adapter at line 255 will throw a generic InvalidCastException if the adapter doesn't implement T. Add an explicit type check with a descriptive error message:

♻️ Proposed type check for the cast
         var adapter = Activator.CreateInstance(adapterInfo.Type, inputInstance);
-        
-        return (T)adapter;
+
+        if (adapter is not T typedAdapter)
+            throw new BitweenException(
+                $"Native adapter '{adapterId}' of type {adapterInfo.Type.Name} does not implement {typeof(T).Name}");
+        return typedAdapter;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Services/XchangeService.cs` around lines 207 - 256,
InstantiateNativeAdapter duplicates logic from
ReceivingService.InstantiateNativeReceiver, swallows conversion exceptions, and
performs an unsafe cast; extract the property-mapping + instantiation logic into
a shared helper on NativeAdapterDiscoveryService (or a new internal helper
class) and have InstantiateNativeAdapter and
ReceivingService.InstantiateNativeReceiver call that helper (move steps that
create inputInstance, map properties, and create adapter instance into a single
method); replace the bare catch around Convert.ChangeType with error handling
that either logs the conversion error (including property name, target type, and
value) or rethrows a descriptive exception for non-string targets; and before
returning cast (T)adapter in InstantiateNativeAdapter validate that adapter is T
(e.g., use is/as checks) and throw a BitweenException with a clear message if
the adapter does not implement the expected interface/type.
SW.Bitween.Api/Services/XchangeService.cs-110-113 (1)

110-113: ⚠️ Potential issue | 🟠 Major

Fix spacing and forward gateway context parameters through the xchange creation flow.

The spacing issue is confirmed (line 111: missing space after comma before GlobalAdapterValuesSet; line 113: missing space after comma before globalAdapterValuesSets).

More importantly, GatewayController has access to both partner and globalAdapterValuesSet (available from cache and authorization context) but doesn't forward them through the xchange creation chain. Currently:

  • SubmitSubscriptionXchange is called without the gatewayPartner parameter
  • SubmitSubscriptionXchange doesn't accept globalAdapterValuesSets at all
  • Both parameters reach CreateXchange as null for all gateway-originated exchanges

If these parameters are meant to carry partner/global adapter context into the xchange, the entire flow needs updating: SubmitSubscriptionXchange should accept both parameters and forward them to CreateXchange, and GatewayController should pass them.

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

In `@SW.Bitween.Api/Services/XchangeService.cs` around lines 110 - 113, Fix the
spacing typos and propagate the gateway context through the call chain: add the
missing spaces after commas around GlobalAdapterValuesSet and
globalAdapterValuesSets in the CreateXchange signature, update
SubmitSubscriptionXchange to accept parameters Partner gatewayPartner and
GlobalAdapterValuesSet[] globalAdapterValuesSets, and forward those arguments
from SubmitSubscriptionXchange into CreateXchange (preserving existing
parameters and ordering). Also update GatewayController to pass the
cached/authorized partner and globalAdapterValuesSets into
SubmitSubscriptionXchange so gateway-originated exchanges receive non-null
gatewayPartner and globalAdapterValuesSets.
🟡 Minor comments (14)
SW.Bitween.PgSql/SW.Bitween.PgSql.csproj-20-23 (1)

20-23: ⚠️ Potential issue | 🟡 Minor

Remove unnecessary Compile Remove entries that reference non-existent migration files.

The excluded migration files Migrations\20260212153331_ApiGateway.cs and Migrations\20260212153331_ApiGateway.Designer.cs do not exist in the repository. These Compile Remove entries serve no purpose and should be deleted from the csproj file.

Csproj snippet
  <ItemGroup>
    <Compile Remove="Migrations\20260212153331_ApiGateway.cs" />
    <Compile Remove="Migrations\20260212153331_ApiGateway.Designer.cs" />
  </ItemGroup>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.PgSql/SW.Bitween.PgSql.csproj` around lines 20 - 23, The csproj
contains two unused Compile Remove entries referencing non-existent migration
files (the XML elements Compile Remove="Migrations\20260212153331_ApiGateway.cs"
and Compile Remove="Migrations\20260212153331_ApiGateway.Designer.cs"); remove
those two <Compile Remove="..."/> lines from the ItemGroup in
SW.Bitween.PgSql.csproj so the project file no longer references missing
migration sources and save the csproj.
SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs-128-147 (1)

128-147: ⚠️ Potential issue | 🟡 Minor

Missing default case in ReadObject switch — unexpected tokens are silently ignored.

If an unexpected token type appears (e.g., EndArray inside an object), the loop silently skips it. Add a default case that throws JsonSerializationException for consistency with ReadValue.

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

In `@SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs` around lines 128 -
147, In DictionaryConverter.cs inside the ReadObject method add a default case
to the switch on reader.TokenType that throws a JsonSerializationException
(similar to ReadValue) when an unexpected token is encountered; include the
unexpected reader.TokenType (and optionally reader.Path) in the exception
message so unexpected tokens (e.g., EndArray) are not silently ignored and
debugging matches ReadValue's behavior.
SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs-36-49 (1)

36-49: ⚠️ Potential issue | 🟡 Minor

Silent data loss when value is not IDictionary<string, object>.

If the cast on line 39 fails, an empty {} is written. Consider throwing or falling back to serializer.Serialize(writer, value) instead of silently writing an empty object. Same concern applies to WriteArray (line 54).

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

In `@SW.Bitween.NativeAdapters/Services/DictionaryConverter.cs` around lines 36 -
49, The current WriteObject method silently writes an empty {} when value is not
an IDictionary<string, object>; update WriteObject (and similarly WriteArray) to
detect a failed cast and either throw a descriptive exception or delegate to the
JsonSerializer (e.g., call serializer.Serialize(writer, value)) so the actual
value is preserved; locate the methods WriteObject and WriteArray in
DictionaryConverter.cs and replace the silent-empty-object behavior with a
fallback to serializer.Serialize(writer, value) (or throw) when the value is not
the expected dictionary/collection.
SW.Bitween.Web/Properties/launchSettings.json-24-24 (1)

24-24: ⚠️ Potential issue | 🟡 Minor

launchUrl uses HTTPS on the HTTP port.

launchUrl is https://localhost:5003 but applicationUrl binds HTTPS to port 5002 and HTTP to port 5003. The launchUrl should be either https://localhost:5002 or http://localhost:5003.

Same issue applies to the PmmDev profile (Line 50).

Also applies to: 45-45, 50-50, 71-71

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

In `@SW.Bitween.Web/Properties/launchSettings.json` at line 24, The
launchSettings.json has mismatched launchUrl values: update each profile's
"launchUrl" to match the scheme and port used in that profile's "applicationUrl"
bindings (e.g., if applicationUrl binds HTTPS to port 5002 and HTTP to port
5003, use "https://localhost:5002" for HTTPS launches or "http://localhost:5003"
for HTTP launches). Fix the IIS Express profile and the PmmDev profile (and the
other occurrences flagged) so launchUrl uses the same scheme and port as the
corresponding applicationUrl entries.
SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs-37-48 (1)

37-48: ⚠️ Potential issue | 🟡 Minor

Null-dereference risk when AuthType is set but corresponding credentials are null.

For "Basic" auth, if LoginUsername or LoginPassword is null, the concatenation produces a broken credential string. For "Bearer", if LoginPassword is null, the Authorization header value will be "Bearer " with no token. Similarly, ClientId!/ClientSecret! on lines 70-71 suppress null warnings without any guard. Consider validating required fields per auth type early in Handle or in the constructor.

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

In `@SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs` around lines 37 - 48,
Validate required credentials for each auth mode before setting headers in
HttpHandler.Handle: when _options.AuthType == "ApiKey" ensure _options.ApiKey is
non-null/non-empty; when "Bearer" ensure _options.LoginPassword (token) is
non-null/non-empty before assigning DefaultRequestHeaders.Authorization; when
"Basic" ensure both _options.LoginUsername and _options.LoginPassword are
non-null and non-empty before building the Base64 credential string; similarly
validate ClientId and ClientSecret (used where ClientId!/ClientSecret! are
referenced) and throw or return a clear ArgumentException/invalid-request result
if missing. Add these checks at the start of the Handle method (or in the
HttpHandler constructor) so header-setting code (DefaultRequestHeaders.Add and
DefaultRequestHeaders.Authorization) never receives null values.
SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs-56-64 (1)

56-64: ⚠️ Potential issue | 🟡 Minor

Dead code after EnsureSuccessStatusCode() — the status check on line 59 is unreachable.

EnsureSuccessStatusCode() (line 58) already throws HttpRequestException for any non-2xx status. The subsequent check on line 59 will never be true. Also, LoginUrl is null-forgiving but there's no prior validation that it's non-null when AuthType == "Login".

Proposed fix
             HttpResponseMessage loginResponse = await client.PostAsync(new Uri(_options.LoginUrl!),
                 new StringContent(loginJson, Encoding.UTF8, "application/json"));
             loginResponse.EnsureSuccessStatusCode();
-            if (loginResponse.StatusCode != HttpStatusCode.OK)
-                throw new Exception(loginResponse.StatusCode.ToString());
             string rs = await loginResponse.Content.ReadAsStringAsync();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs` around lines 56 - 64,
The code calls loginResponse.EnsureSuccessStatusCode(), making the subsequent
explicit status check on loginResponse.StatusCode unreachable; remove that
redundant check and instead validate inputs and response payloads: before
calling PostAsync ensure _options.LoginUrl is not null when performing login
(check the AuthType == "Login" path and throw a clear exception if LoginUrl is
null), keep EnsureSuccessStatusCode() to handle non-success HTTP responses, and
after reading and deserializing the body (loginResponse and rsDeserialized)
validate rsDeserialized and its Jwt before assigning
client.DefaultRequestHeaders.Authorization and throw or return an informative
error if Jwt is missing.
SW.Bitween.Api/Resources/ApiGateways/Update.cs-34-38 (1)

34-38: ⚠️ Potential issue | 🟡 Minor

UrlName is validated but Name is not.

Name can be set to null or empty without any guard. If Name is required, add a similar check. Consider using FluentValidation (as done in other handlers) for consistency.

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

In `@SW.Bitween.Api/Resources/ApiGateways/Update.cs` around lines 34 - 38, The
handler currently validates model.UrlName but not model.Name, allowing
entity.Name to become null/empty; add a guard for model.Name (e.g., check
string.IsNullOrWhiteSpace(model.Name) and throw SWException with an appropriate
message) before assigning entity.Name = model.Name, or, for consistency with
other handlers, move the validation into the existing FluentValidation validator
used for this Update handler so both Name and UrlName are validated together.
SW.Bitween.Api/Resources/ApiGateways/Delete.cs-20-26 (1)

20-26: ⚠️ Potential issue | 🟡 Minor

Deletion will throw a raw DB exception if the gateway has associated partners.

Per the EF config in BitweenDbContext.cs (lines 29-34), ApiGatewayPartner → ApiGateway uses DeleteBehavior.Restrict. Attempting to delete a gateway with existing partners will result in an unhandled DbUpdateException. Consider either removing partners first or catching the exception to return a user-friendly error.

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

In `@SW.Bitween.Api/Resources/ApiGateways/Delete.cs` around lines 20 - 26, Handle
currently calls DeleteByKeyAsync<ApiGateway> which will bubble up a
DbUpdateException due to the ApiGatewayPartner → ApiGateway relationship
configured with DeleteBehavior.Restrict in BitweenDbContext; update Handle to
either (a) delete dependent ApiGatewayPartner rows first by querying the
ApiGatewayPartner set for partners with the gateway id and removing them before
calling DeleteByKeyAsync<ApiGateway>, or (b) wrap the
DeleteByKeyAsync<ApiGateway> call in a try/catch for DbUpdateException and
return/throw a clear user-facing error indicating the gateway has associated
partners (referencing the Handle method, DeleteByKeyAsync<ApiGateway>,
ApiGatewayPartner, ApiGateway, and BitweenDbContext to locate the relevant
code).
SW.Bitween.Api/Domain/Partner/Partner.cs-33-33 (1)

33-33: ⚠️ Potential issue | 🟡 Minor

AdapterProperties is not initialized and may cause NullReferenceException.

Other collections (_Subscriptions, _ApiCredentials) are initialized in the constructor, but AdapterProperties is left as null. Any code that iterates or accesses this dictionary without a null check will throw.

Consider initializing it:

Proposed fix
-        public Dictionary<string,string> AdapterProperties { get; set; }
+        public Dictionary<string,string> AdapterProperties { get; set; } = new();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Domain/Partner/Partner.cs` at line 33,
Partner.AdapterProperties is left uninitialized and can cause
NullReferenceException; initialize the dictionary in the Partner constructor to
ensure safe access. In the Partner class constructor (the method named Partner
or the class initializer that already sets _Subscriptions and _ApiCredentials),
instantiate AdapterProperties = new Dictionary<string,string>() so any callers
can safely iterate or add entries without null checks. Ensure any
deserialization paths preserve non-null by keeping this initialization in the
constructor or by using a property initializer on the AdapterProperties
property.
SW.Bitween.Api/Resources/ApiGateways/Get.cs-19-41 (1)

19-41: ⚠️ Potential issue | 🟡 Minor

Missing access control check; consider making public read intentional.

Delete and Update both enforce _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member), but this handler has no access check. Since Get doesn't inject IRequestContext, adding the guard would require refactoring the constructor. If public read is intentional, add a comment to clarify. Otherwise, inject IRequestContext and add the guard.

Also, the .Include() / .ThenInclude() calls on lines 23–26 are redundant; EF Core automatically loads these navigation properties when the Select projection accesses them (e.g., p.Partner.Name). Remove them for clarity.

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

In `@SW.Bitween.Api/Resources/ApiGateways/Get.cs` around lines 19 - 41, The Handle
method in Get (public async Task<object> Handle(int key)) lacks the access
control present in Delete/Update; either make read explicitly public by adding a
clarifying comment above Handle/ApiGateway GET behavior, or inject
IRequestContext into the handler constructor and call
_requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member) at the start
of Handle to enforce the same guard as Delete/Update; additionally remove the
redundant .Include(...).ThenInclude(...) calls (the projection that uses
p.Partner.Name and p.Subscription.Name loads them) to simplify the query.
SW.Bitween.Api/Services/ReceivingService.cs-150-150 (1)

150-150: ⚠️ Potential issue | 🟡 Minor

Activator.CreateInstance(inputType) will throw if the input model has no parameterless constructor.

If the adapter's input model type doesn't have a public parameterless constructor, this call throws a MissingMethodException. Consider adding a guard or a more descriptive error message.

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

In `@SW.Bitween.Api/Services/ReceivingService.cs` at line 150, The call to
Activator.CreateInstance(inputType) in ReceivingService will throw
MissingMethodException if the adapter input model lacks a public parameterless
constructor; add a guard: check inputType for a public parameterless constructor
(e.g., via inputType.GetConstructor(Type.EmptyTypes)) before calling
Activator.CreateInstance, and if none exists throw a clear
InvalidOperationException that includes inputType.FullName and guidance to add a
public parameterless ctor (alternatively wrap Activator.CreateInstance in a
try/catch that catches MissingMethodException and rethrows a descriptive
exception referencing inputType and the receiving method).
SW.Bitween.Api/Resources/ApiGateways/AddPartner.cs-44-57 (1)

44-57: ⚠️ Potential issue | 🟡 Minor

Missing validation that PartnerId exists.

The handler validates that the gateway and subscription exist but never checks whether model.PartnerId refers to a valid Partner. If an invalid PartnerId is supplied, the foreign key constraint at SaveChangesAsync will throw a raw DbUpdateException, which is harder for the caller to interpret than a proper SWNotFoundException.

This is consistent with the pattern used for SubscriptionId validation (lines 35–39).

Proposed fix — add Partner existence check
+            var partner = await _dbContext.Set<Partner>()
+                .FirstOrDefaultAsync(p => p.Id == model.PartnerId);
+
+            if (partner == null)
+                throw new SWNotFoundException($"Partner with Id {model.PartnerId} not found");
+
             // Check if partner already exists
             var existingPartner = gateway.Partners != null 
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/ApiGateways/AddPartner.cs` around lines 44 - 57, Add
a check that the supplied model.PartnerId refers to an existing Partner before
creating ApiGatewayPartner to avoid DbUpdateException; in the AddPartner handler
(the method creating partnerLink/gateway variable), query the partners table
(e.g., await _dbContext.Partners.AnyAsync(p => p.PartnerId == model.PartnerId)
or FindAsync) similar to the SubscriptionId validation, and if not found throw a
SWNotFoundException("Partner not found" or similar); place this check before
constructing ApiGatewayPartner and before SaveChangesAsync so the error is
surfaced as a SWNotFoundException.
SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.cs-47-47 (1)

47-47: ⚠️ Potential issue | 🟡 Minor

Add HasMaxLength(200) constraint to PgSql DbContext configuration for GlobalAdapterValuesSet.Id.

The PgSql BitweenDbContext entity configuration for GlobalAdapterValuesSet is missing the max length constraint. The main BitweenDbContext specifies .HasMaxLength(200) on the Id property, and the MsSql/MySql migrations both use varchar(200). The PgSql DbContext must include this configuration to maintain consistency across providers:

gav.Property(p => p.Id).IsUnicode(false).HasMaxLength(200);

Without this, the property defaults to unbounded text in PostgreSQL, creating an inconsistency in primary key size constraints across databases.

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

In `@SW.Bitween.PgSql/Migrations/20260217152930_ApiGateWayAndGlobalValues.cs` at
line 47, The PgSql DbContext is creating GlobalAdapterValuesSet.Id as unbounded
text; update the BitweenDbContext PostgreSQL entity configuration for
GlobalAdapterValuesSet (the EntityTypeBuilder referenced as gav) to apply
.Property(p => p.Id).IsUnicode(false).HasMaxLength(200) so the column maps to
varchar(200) like MsSql/MySql; after changing the model, regenerate or adjust
the migration so the column definition is varchar(200) instead of text to keep
provider consistency.
SW.Bitween.Api/Controllers/GatewayController.cs-60-60 (1)

60-60: ⚠️ Potential issue | 🟡 Minor

StreamReader is not disposed — potential resource leak.

new StreamReader(HttpContext.Request.Body) is never disposed. While it may not cause issues in practice (the framework manages the request body lifetime), it's best practice to use a using statement. Note: pass leaveOpen: true to avoid closing the underlying request stream prematurely.

🛡️ Proposed fix
-        var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
+        using var reader = new StreamReader(HttpContext.Request.Body, leaveOpen: true);
+        var json = await reader.ReadToEndAsync();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Controllers/GatewayController.cs` at line 60, The StreamReader
created for HttpContext.Request.Body in GatewayController is not disposed;
change the code to use a using declaration or using block (e.g., using var
reader = new StreamReader(HttpContext.Request.Body, leaveOpen: true);) and then
call await reader.ReadToEndAsync() to get json so the StreamReader is properly
disposed while leaving the underlying request stream open.

Comment thread SW.Bitween.Api/Controllers/GatewayController.cs
Comment thread SW.Bitween.Api/Helpers/StartupValuesFiller.cs
Comment on lines +44 to +52
var partnerLink = gateway.Partners?
.FirstOrDefault(p => p.PartnerId == model.PartnerId);

if (partnerLink == null)
throw new SWNotFoundException($"Partner with Id {model.PartnerId} not found in gateway {gatewayId}");

partnerLink.SubscriptionId = model.SubscriptionId;

await _dbContext.SaveChangesAsync();

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 | 🔴 Critical

Modifying a primary key component — this will fail at runtime.

ApiGatewayPartner has a composite primary key (ApiGatewayId, PartnerId, SubscriptionId) (see SW.Bitween.PgSql/BitweenDbContext.cs line 134). EF Core does not allow modifying key properties on tracked entities. Setting partnerLink.SubscriptionId on line 50 will throw an InvalidOperationException when SaveChangesAsync() is called.

You need to remove the old entity and add a new one instead.

Proposed fix
             var partnerLink = gateway.Partners?
                 .FirstOrDefault(p => p.PartnerId == model.PartnerId);
 
             if (partnerLink == null)
                 throw new SWNotFoundException($"Partner with Id {model.PartnerId} not found in gateway {gatewayId}");
 
-            partnerLink.SubscriptionId = model.SubscriptionId;
+            _dbContext.Remove(partnerLink);
+
+            var newPartnerLink = new ApiGatewayPartner
+            {
+                ApiGatewayId = gatewayId,
+                PartnerId = model.PartnerId,
+                SubscriptionId = model.SubscriptionId
+            };
+            _dbContext.Add(newPartnerLink);
 
             await _dbContext.SaveChangesAsync();
📝 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
var partnerLink = gateway.Partners?
.FirstOrDefault(p => p.PartnerId == model.PartnerId);
if (partnerLink == null)
throw new SWNotFoundException($"Partner with Id {model.PartnerId} not found in gateway {gatewayId}");
partnerLink.SubscriptionId = model.SubscriptionId;
await _dbContext.SaveChangesAsync();
var partnerLink = gateway.Partners?
.FirstOrDefault(p => p.PartnerId == model.PartnerId);
if (partnerLink == null)
throw new SWNotFoundException($"Partner with Id {model.PartnerId} not found in gateway {gatewayId}");
_dbContext.Remove(partnerLink);
var newPartnerLink = new ApiGatewayPartner
{
ApiGatewayId = gatewayId,
PartnerId = model.PartnerId,
SubscriptionId = model.SubscriptionId
};
_dbContext.Add(newPartnerLink);
await _dbContext.SaveChangesAsync();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/ApiGateways/UpdatePartner.cs` around lines 44 - 52,
The code attempts to update a key property on a tracked ApiGatewayPartner
(setting partnerLink.SubscriptionId) which will throw; instead remove the
existing partnerLink entity from the DbContext and add a new ApiGatewayPartner
with the same ApiGatewayId and PartnerId but the new SubscriptionId (preserving
any other non-key fields you need), e.g. call _dbContext.Remove(partnerLink)
then _dbContext.Add(new ApiGatewayPartner { ApiGatewayId = gatewayId, PartnerId
= model.PartnerId, SubscriptionId = model.SubscriptionId, /* copy other fields
if needed */ }) and then await _dbContext.SaveChangesAsync(); ensure you
reference the partnerLink variable and the ApiGatewayPartner type when
implementing the replace.

Comment thread SW.Bitween.Api/Resources/Subscriptions/Create.cs
Comment on lines +958 to +965
b1.HasData(
new
{
PartnerId = 1,
Id = 1,
Key = "7facc758283844b49cc4ffd26a75b1de",
Name = "default"
});

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 | 🔴 Critical

Remove hardcoded API credential from seed data.
The migration snapshot embeds a real-looking API key in PartnerApiCredentials. This is a secret in source control and will be deployed into databases. Rotate it and source the value from a secrets store or a post-deploy provisioning step instead of migrations.

🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 963-963: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

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

In
`@SW.Bitween.MsSql/Migrations/20260217150741_ApiGateWayAndGlobalValues.Designer.cs`
around lines 958 - 965, The migration snapshot currently seeds a real API
credential via b1.HasData(...) for PartnerApiCredentials (PartnerId/Id/Key/Name)
in the ApiGateWayAndGlobalValues.Designer.cs migration; remove the hardcoded Key
value from the seed data and replace it with a non-secret placeholder or omit
seeding the Key entirely, then move creation of real credentials into a secure
post-deploy provisioning step or read from a secrets store at runtime (e.g.,
change the b1.HasData call to exclude Key or use a placeholder and document that
PartnerApiCredentials must be populated from secrets management after
deployment).

Comment on lines +955 to +962
b1.HasData(
new
{
PartnerId = 1,
Id = 1,
Key = "7facc758283844b49cc4ffd26a75b1de",
Name = "default"
});

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 | 🔴 Critical

Remove hardcoded API credential from seed data.
This snapshot embeds a real-looking API key in PartnerApiCredentials. Avoid committing secrets in migrations; rotate the key and inject it via a secrets store or post-deploy provisioning.

🧰 Tools
🪛 Gitleaks (8.30.0)

[high] 960-960: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

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

In
`@SW.Bitween.MySql/Migrations/20260217151131_ApiGateWayAndGlobalValues.Designer.cs`
around lines 955 - 962, The migration currently seeds a real-looking API
credential via b1.HasData into the PartnerApiCredentials table
(PartnerId/Id/Key/Name); remove the hardcoded Key value from the migration and
replace it with a non-secret placeholder or remove the seeded secret row
entirely, then ensure runtime provisioning uses a secrets store or post-deploy
script to insert rotated credentials; update the migration method that calls
b1.HasData (the block inserting PartnerId = 1, Id = 1, Key = "...", Name =
"default") to omit the actual secret and document that credentials are injected
out-of-band.

Comment thread SW.Bitween.NativeAdapters/HttpHandler/HttpHandler.cs
Comment thread SW.Bitween.Web/Properties/launchSettings.json Outdated

@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

🧹 Nitpick comments (4)
SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs (2)

1-5: Add using SW.Bitween.Domain.Accounts; to avoid fully qualified AccountRole references on Line 22.

The sibling Create.cs already imports this namespace. The verbose Domain.Accounts.AccountRole.* qualified names are purely a result of the missing directive.

♻️ Proposed fix
 using System.Threading.Tasks;
 using Microsoft.EntityFrameworkCore;
 using SW.Bitween.Domain;
+using SW.Bitween.Domain.Accounts;
 using SW.Bitween.Model;
 using SW.PrimitiveTypes;

Then simplify Line 22:

-            _requestContext.EnsureAccess(Domain.Accounts.AccountRole.Admin, Domain.Accounts.AccountRole.Member, Domain.Accounts.AccountRole.Viewer);
+            _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member, AccountRole.Viewer);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs` around lines 1 - 5,
Add the missing using directive for the Accounts namespace to remove
fully-qualified enum references: add "using SW.Bitween.Domain.Accounts;" at the
top of SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs so you can
replace occurrences of Domain.Accounts.AccountRole.* with the simpler
AccountRole.* (matching the sibling Create.cs import); update any AccountRole
references (e.g., the one on the current Line 22) to the unqualified form after
adding the using.

1-5: Add using SW.Bitween.Domain.Accounts; to avoid fully qualified type names on Line 22.

The sibling Create.cs already imports this namespace. The fully qualified Domain.Accounts.AccountRole.* names on Line 22 are purely a result of the missing using directive.

♻️ Proposed fix
 using System.Threading.Tasks;
 using Microsoft.EntityFrameworkCore;
 using SW.Bitween.Domain;
+using SW.Bitween.Domain.Accounts;
 using SW.Bitween.Model;
 using SW.PrimitiveTypes;

Then simplify Line 22:

-            _requestContext.EnsureAccess(Domain.Accounts.AccountRole.Admin, Domain.Accounts.AccountRole.Member, Domain.Accounts.AccountRole.Viewer);
+            _requestContext.EnsureAccess(AccountRole.Admin, AccountRole.Member, AccountRole.Viewer);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs` around lines 1 - 5,
Add the missing using directive "SW.Bitween.Domain.Accounts" at the top of
Get.cs and then simplify the fully-qualified references on the line using
Domain.Accounts.AccountRole.* to just AccountRole.* (mirror the sibling
Create.cs import), so occurrences of Domain.Accounts.AccountRole should be
replaced with AccountRole to remove fully-qualified type names.
SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs (2)

51-51: Values is validated as NotNull but not NotEmpty — empty dictionaries are accepted.

If a GlobalAdapterValuesSet with zero entries has no meaningful purpose, add an NotEmpty() rule. Leave as-is if empty sets are a valid initial state (e.g., a template to be filled later).

♻️ Proposed addition
-                RuleFor(i => i.Values).NotNull();
+                RuleFor(i => i.Values).NotNull().NotEmpty();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs` at line 51, The
validator currently only enforces RuleFor(i => i.Values).NotNull(), allowing
empty dictionaries; update the validation on the Values property in Create (the
validator where RuleFor(i => i.Values) is defined) to require content by adding
NotEmpty(), e.g. change or extend the rule to RuleFor(i =>
i.Values).NotNull().NotEmpty(); (or add a separate RuleFor(i =>
i.Values).NotEmpty() after the NotNull() call) unless empty sets are
intentionally allowed.

51-51: Values is validated as NotNull but not NotEmpty — an empty dictionary is accepted.

If a GlobalAdapterValuesSet with zero entries has no meaningful purpose, add NotEmpty(). Leave as-is if empty sets are valid as an initial template.

♻️ Proposed addition
-                RuleFor(i => i.Values).NotNull();
+                RuleFor(i => i.Values).NotNull().NotEmpty();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs` at line 51, The
validator currently only enforces RuleFor(i => i.Values).NotNull() in Create.cs
for GlobalAdapterValuesSet, allowing an empty dictionary; update the rule to
also require NotEmpty() so empty sets are rejected when not meaningful—for
example change the RuleFor for Values (in the Create validator) to call
.NotNull().NotEmpty() (or add a separate RuleFor(i => i.Values).NotEmpty()) so
the Values dictionary must contain at least one entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs`:
- Around line 26-28: The current pre-insert check using AnyAsync on
_dbContext.Set<GlobalAdapterValuesSet>() is vulnerable to TOCTOU races: wrap the
call to SaveChangesAsync (or the method that persists the new
GlobalAdapterValuesSet) in a try-catch that catches DbUpdateException and detect
a unique/PK constraint violation (inspect the inner exception / SQL error codes
for your DB provider); when such a duplicate-key error is detected, rethrow an
SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id
'{request.Id}' already exists") so concurrent duplicate requests return the same
validation error as the pre-check (keep the original AnyAsync check but add this
catch-and-translate around the persistence call).
- Around line 26-28: The current duplicate-ID race (checked via
_dbContext.Set<GlobalAdapterValuesSet>().AnyAsync) can still cause a
DbUpdateException at SaveChangesAsync; wrap the SaveChangesAsync call in a
try/catch that catches DbUpdateException, inspect the exception/inner exception
for a uniqueness/PK violation related to GlobalAdapterValuesSet.Id, and rethrow
a SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id
'{request.Id}' already exists") when detected; otherwise rethrow the original
exception so non-constraint errors still bubble up.

---

Nitpick comments:
In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs`:
- Line 51: The validator currently only enforces RuleFor(i =>
i.Values).NotNull(), allowing empty dictionaries; update the validation on the
Values property in Create (the validator where RuleFor(i => i.Values) is
defined) to require content by adding NotEmpty(), e.g. change or extend the rule
to RuleFor(i => i.Values).NotNull().NotEmpty(); (or add a separate RuleFor(i =>
i.Values).NotEmpty() after the NotNull() call) unless empty sets are
intentionally allowed.
- Line 51: The validator currently only enforces RuleFor(i =>
i.Values).NotNull() in Create.cs for GlobalAdapterValuesSet, allowing an empty
dictionary; update the rule to also require NotEmpty() so empty sets are
rejected when not meaningful—for example change the RuleFor for Values (in the
Create validator) to call .NotNull().NotEmpty() (or add a separate RuleFor(i =>
i.Values).NotEmpty()) so the Values dictionary must contain at least one entry.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs`:
- Around line 1-5: Add the missing using directive for the Accounts namespace to
remove fully-qualified enum references: add "using SW.Bitween.Domain.Accounts;"
at the top of SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Get.cs so you can
replace occurrences of Domain.Accounts.AccountRole.* with the simpler
AccountRole.* (matching the sibling Create.cs import); update any AccountRole
references (e.g., the one on the current Line 22) to the unqualified form after
adding the using.
- Around line 1-5: Add the missing using directive "SW.Bitween.Domain.Accounts"
at the top of Get.cs and then simplify the fully-qualified references on the
line using Domain.Accounts.AccountRole.* to just AccountRole.* (mirror the
sibling Create.cs import), so occurrences of Domain.Accounts.AccountRole should
be replaced with AccountRole to remove fully-qualified type names.

Comment on lines +26 to +28
var exists = await _dbContext.Set<GlobalAdapterValuesSet>().AnyAsync(x => x.Id == request.Id);
if (exists)
throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists");

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

TOCTOU on duplicate-ID check — concurrent duplicate requests will surface an unhandled DbUpdateException instead of SWValidationException.

Between the AnyAsync check and SaveChangesAsync, a concurrent request with the same Id can slip through. The DB PK constraint rejects the second write, but EF Core raises a raw DbUpdateException, producing a 500 instead of a friendly 400/422 validation error.

🛡️ Proposed fix — catch the constraint violation
             _dbContext.Add(entity);
-            await _dbContext.SaveChangesAsync();
+            try
+            {
+                await _dbContext.SaveChangesAsync();
+            }
+            catch (DbUpdateException)
+            {
+                throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists");
+            }
             return new
             {
                 entity.Id
             };
📝 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
var exists = await _dbContext.Set<GlobalAdapterValuesSet>().AnyAsync(x => x.Id == request.Id);
if (exists)
throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists");
var exists = await _dbContext.Set<GlobalAdapterValuesSet>().AnyAsync(x => x.Id == request.Id);
if (exists)
throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists");
// ... entity construction ...
_dbContext.Add(entity);
try
{
await _dbContext.SaveChangesAsync();
}
catch (DbUpdateException)
{
throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists");
}
return new
{
entity.Id
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs` around lines 26 -
28, The current pre-insert check using AnyAsync on
_dbContext.Set<GlobalAdapterValuesSet>() is vulnerable to TOCTOU races: wrap the
call to SaveChangesAsync (or the method that persists the new
GlobalAdapterValuesSet) in a try-catch that catches DbUpdateException and detect
a unique/PK constraint violation (inspect the inner exception / SQL error codes
for your DB provider); when such a duplicate-key error is detected, rethrow an
SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id
'{request.Id}' already exists") so concurrent duplicate requests return the same
validation error as the pre-check (keep the original AnyAsync check but add this
catch-and-translate around the persistence call).

⚠️ Potential issue | 🟡 Minor

TOCTOU on duplicate-ID check — concurrent duplicate requests will surface a raw DbUpdateException instead of a friendly SWValidationException.

Between the AnyAsync check and SaveChangesAsync, a concurrent request with the same Id can slip through. The database PK constraint will reject the second write, but EF Core surfaces that as an unhandled DbUpdateException, causing a 500 response instead of a 400/422 validation error.

🛡️ Proposed fix — catch the DB uniqueness violation
             _dbContext.Add(entity);
-            await _dbContext.SaveChangesAsync();
+            try
+            {
+                await _dbContext.SaveChangesAsync();
+            }
+            catch (DbUpdateException)
+            {
+                throw new SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id '{request.Id}' already exists");
+            }
             return new
             {
                 entity.Id
             };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@SW.Bitween.Api/Resources/GlobalAdapterValuesSets/Create.cs` around lines 26 -
28, The current duplicate-ID race (checked via
_dbContext.Set<GlobalAdapterValuesSet>().AnyAsync) can still cause a
DbUpdateException at SaveChangesAsync; wrap the SaveChangesAsync call in a
try/catch that catches DbUpdateException, inspect the exception/inner exception
for a uniqueness/PK violation related to GlobalAdapterValuesSet.Id, and rethrow
a SWValidationException("ID_EXISTS", $"GlobalAdapterValuesSet with id
'{request.Id}' already exists") when detected; otherwise rethrow the original
exception so non-constraint errors still bubble up.

@AhmadRAbuhussein
AhmadRAbuhussein merged commit 1a3e39f into releases/r8.0 Feb 25, 2026
3 checks passed
@AhmadRAbuhussein
AhmadRAbuhussein deleted the muhannad/api-gateway branch February 25, 2026 10:34
@coderabbitai coderabbitai Bot mentioned this pull request Jul 5, 2026
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.

3 participants