Skip to content

Add composite store support for applications#1396

Open
rajithacharith wants to merge 1 commit intoasgardeo:mainfrom
rajithacharith:composite-pagination
Open

Add composite store support for applications#1396
rajithacharith wants to merge 1 commit intoasgardeo:mainfrom
rajithacharith:composite-pagination

Conversation

@rajithacharith
Copy link
Contributor

@rajithacharith rajithacharith commented Feb 13, 2026

Purpose

Add composite store support for Applications

Approach

This pull request introduces support for a composite application store that combines both file-based (declarative/immutable) and database (mutable) sources. It also adds new error handling for composite mode and improves validation logic to prevent duplicate application IDs across stores. Additionally, it updates mocks and interfaces to support the new composite functionality and clarifies how application store mode is determined.

Key changes include:

Composite Store Implementation:

  • Added compositeApplicationStore in composite_store.go, which merges file-based and database-backed application stores. Read operations merge results from both stores, while write operations only affect the database store. Includes logic to prevent modification of declarative (YAML-based) applications and deduplicate results.

Store Mode Configuration:

  • Added config.go to determine the application store mode (mutable, declarative, or composite) based on service-level or global configuration, with clear resolution order.

Interface & Mock Updates:

  • Extended applicationStoreInterface and its mock to include IsApplicationExists, IsApplicationExistsByName, and IsApplicationDeclarative methods for composite store compatibility. Also implemented these in cachedBackedApplicationStore. [1] [2]

Declarative Resource Loading & Validation:

  • Updated loadDeclarativeResources to accept both file and database stores, enabling duplicate ID checks across both in composite mode. Added validateApplicationWrapper to enforce unique IDs and basic validation for declarative applications. [1] [2]
  • Modified GetAllResourceIDs to exclude declarative (read-only) applications in composite mode.

Error Handling Enhancements:

  • Introduced new error constants for composite mode, including result limit exceeded and attempts to modify declarative resources. [1] [2]

Related Issues

  • N/A

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Support for composite application storage (file + DB), selectable store modes, and new config option to choose mode
    • File-backed applications now exposed as read-only (IsReadOnly) in API responses
  • Error Handling

    • Prevents creation/updates/deletes of declarative (read-only) applications; maps composite-list limits to a new public error
  • Tests

    • Added mocks for existence and declarative checks to improve test coverage

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds multi-mode application storage (mutable DB, declarative file, composite), three new store interface methods for existence/declarative checks, implementations across stores, composite store merging logic, config-driven store selection, service guards for read-only apps, new errors, and updated mocks.

Changes

Cohort / File(s) Summary
Store Interface & Implementations
backend/internal/application/store.go, backend/internal/application/file_based_store.go, backend/internal/application/cache_backed_store.go
Added interface methods IsApplicationExists, IsApplicationExistsByName, IsApplicationDeclarative and implemented them for DB, file, and cache-backed stores. File store treats entries as declarative; DB store returns mutable.
Composite Store
backend/internal/application/composite_store.go
New compositeApplicationStore combining file and DB stores: cross-store reads (merge/deduplicate, mark IsReadOnly), DB-only writes, existence/declarative checks with file-store precedence, and enforced list/result limits.
Configuration & Initialization
backend/internal/application/config.go, backend/internal/application/init.go, backend/internal/system/config/config.go
Added Application.Store config and getApplicationStoreMode(); introduced initializeStore() to wire mutable, declarative, or composite stores based on mode.
Declarative Resource Loading & Validation
backend/internal/application/declarative_resource.go
Adjusted loader to extract file/db stores for cross-store validation, added validateApplicationWrapper to detect duplicate IDs across stores, and filtered declarative apps where appropriate.
Service Layer & Models
backend/internal/application/service.go, backend/internal/application/model/application.go
Added IsReadOnly to DTOs/responses; service now blocks modifications in declarative mode and forbids updates/deletes of declarative apps in composite mode; maps composite list-limit error to client error.
Errors
backend/internal/application/error_constants.go
Introduced ErrResultLimitExceededInCompositeMode, ErrorCannotModifyDeclarativeResource (APP-1028), and ErrorResultLimitExceeded (APP-1029).
Mocks & Tests
backend/tests/mocks/applicationmock/applicationStoreInterface_mock.go, backend/internal/application/applicationStoreInterface_mock_test.go
Generated mocks for the three new interface methods with full testify/mock Expecter, Call types, and Run/Return/RunAndReturn helpers.
Cache-backed store wiring
backend/internal/application/cache_backed_store.go
Added passthrough implementations delegating existence/declarative checks to underlying store (no caching logic).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Service
    participant CompositeStore
    participant DBStore
    participant FileStore

    rect rgba(200, 150, 255, 0.5)
        Note over Client,CompositeStore: GetApplicationList (composite mode)
    end

    Client->>Service: GetApplicationList()
    Service->>CompositeStore: GetApplicationList()
    CompositeStore->>DBStore: GetApplicationList()
    DBStore-->>CompositeStore: dbApps
    CompositeStore->>FileStore: GetApplicationList()
    FileStore-->>CompositeStore: fileApps
    CompositeStore->>CompositeStore: mergeAndDeduplicateApplications(dbApps, fileApps)
    CompositeStore->>CompositeStore: enforceResultLimit(merged)
    alt Limit exceeded
        CompositeStore-->>Service: ErrResultLimitExceededInCompositeMode
        Service-->>Client: ErrorResultLimitExceeded
    else Within limit
        CompositeStore-->>Service: merged []BasicApplicationDTO (IsReadOnly set)
        Service-->>Client: response
    end
Loading
sequenceDiagram
    participant Client
    participant Service
    participant CompositeStore
    participant DBStore
    participant FileStore

    rect rgba(200, 150, 255, 0.5)
        Note over Client,CompositeStore: UpdateApplication (composite mode)
    end

    Client->>Service: UpdateApplication(app)
    Service->>CompositeStore: GetApplicationByID(app.ID)
    CompositeStore->>DBStore: GetApplicationByID(id)
    alt Found in DB
        DBStore-->>CompositeStore: app (IsReadOnly=false)
    else Not in DB
        DBStore-->>CompositeStore: ApplicationNotFoundError
        CompositeStore->>FileStore: GetApplicationByID(id)
        FileStore-->>CompositeStore: app (IsReadOnly=true)
    end
    CompositeStore-->>Service: appDTO
    alt app.IsReadOnly == true
        Service-->>Client: ErrorCannotModifyDeclarativeResource
    else
        Service->>CompositeStore: UpdateApplication(updated)
        CompositeStore->>DBStore: UpdateApplication(updated)
        DBStore-->>CompositeStore: success/error
        CompositeStore-->>Service: result
        Service-->>Client: UpdateApplicationResponse
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Type/Improvement, breaking change

Suggested reviewers

  • ThaminduDilshan
  • darshanasbg
  • senthalan

Poem

🐰 I hopped through files and merged with care,
DB and YAML now dance in the air.
Read-only flags flutter, duplicates fall,
Composite stores answer the call.
A rabbit's cheer for storage that's fair! 🌿

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding composite store support for applications, which aligns with all major components in the changeset.
Description check ✅ Passed The description covers purpose, approach with detailed key changes, and includes most checklist items. However, manual testing, documentation, tests, and security checks are all marked as incomplete.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@backend/internal/application/composite_store.go`:
- Around line 27-42: Move the composite store implementation from
composite_store.go into the module's store.go to follow repo conventions:
relocate the compositeApplicationStore type and its constructor
newCompositeApplicationStore (including fileStore and dbStore fields) into
store.go alongside the applicationStoreInterface and other store
implementations; ensure imports and package declarations are adjusted, and
remove the redundant composite_store.go file or export no duplicate symbols so
only the single implementation in store.go remains.
- Around line 52-67: The GetApplicationList in compositeApplicationStore passes
a hardcoded limit of 100 to
declarativeresource.CompositeMergeListHelperWithLimit which can truncate results
for a non-paginated API; change that 100 to use the composite cap (e.g.
serverconst.MaxCompositeStoreRecords or MaxInt) so the helper can return all
records up to the enforced max, keeping mergeAndDeduplicateApplications and the
existing maxRecords parameter unchanged.

In `@backend/internal/application/declarative_resource.go`:
- Around line 109-115: The call to newApplicationService in
loadDeclarativeResources uses 5 arguments but the function requires 6 (causing a
compile error); update this invocation in loadDeclarativeResources to pass the
same sixth parameter used at the other call site (or add a nil placeholder if
the service doesn’t need a real value in this context) so the argument count
matches newApplicationService’s signature.
- Around line 224-236: The code currently swallows errors from
fileStore.IsApplicationExists and dbStore.IsApplicationExists; update the checks
in the declarative validation (around the IsApplicationExists calls) to return
the underlying error instead of ignoring it—i.e., if err != nil return
fmt.Errorf("failed to check application existence: %w", err) (or similar) for
both fileStore.IsApplicationExists(app.ID) and
dbStore.IsApplicationExists(app.ID), while still returning the duplicate-ID
fmt.Errorf when exists == true; ensure you wrap the original error to preserve
context.

In `@backend/internal/application/error_constants.go`:
- Around line 236-241: The ErrorCannotModifyDeclarativeResource constant
currently reuses the code "APP-1028" already assigned to
ErrorWhileRetrievingFlowDefinition; update the ServiceError.Code value for
ErrorCannotModifyDeclarativeResource to a unique code (for example "APP-1029")
by editing the Code field in the serviceerror.ServiceError literal for
ErrorCannotModifyDeclarativeResource so it no longer conflicts with
ErrorWhileRetrievingFlowDefinition.
- Around line 28-32: Rename the exported sentinel error
ErrResultLimitExceededInCompositeMode to a package-private name (e.g.,
errResultLimitExceededInCompositeMode) and update all references to this symbol
throughout the package to use the new lowercase identifier; ensure the variable
still uses errors.New("result limit exceeded in composite mode") and that any
callers within the package import no changes beyond the identifier rename so the
error remains internal to the package.

In `@backend/internal/application/service.go`:
- Around line 89-92: Update the Update and Delete paths to use the same
application-specific declarative-mode resolution as Create: replace calls to
declarativeresource.CheckDeclarativeUpdate() and
declarativeresource.CheckDeclarativeDelete() with the per-application check used
by Create (isDeclarativeModeEnabled() or an equivalent helper that takes the
Application/Store context), so that Update and Delete honor the resource's Store
setting (mutable/composite) rather than only the global
DeclarativeResources.Enabled flag; ensure you preserve the existing error
returned (&ErrorCannotModifyDeclarativeResource) when the per-app check denies
the operation.
🧹 Nitpick comments (1)
backend/internal/application/init.go (1)

47-49: Consider caching the DB store in composite mode for parity with mutable mode.

Mutable mode wraps the DB store with the cached variant, while composite mode uses the raw DB store. If caching is still desired, wrap the DB store before composing to avoid a performance regression.

♻️ Suggested adjustment
case serverconst.StoreModeComposite:
    fileStore := newFileBasedStore()
-   dbStore := newApplicationStore()
+   dbStore := newCachedBackedApplicationStore(newApplicationStore())
    appStore = newCompositeApplicationStore(fileStore, dbStore)

Also applies to: 68-122

Comment on lines +27 to +42
// compositeApplicationStore implements a composite store that combines file-based (immutable) and database (mutable) stores.
// - Read operations query both stores and merge results
// - Write operations (Create/Update/Delete) only affect the database store
// - Declarative applications (from YAML files) cannot be modified or deleted
type compositeApplicationStore struct {
fileStore applicationStoreInterface
dbStore applicationStoreInterface
}

// newCompositeApplicationStore creates a new composite store with both file-based and database stores.
func newCompositeApplicationStore(fileStore, dbStore applicationStoreInterface) *compositeApplicationStore {
return &compositeApplicationStore{
fileStore: fileStore,
dbStore: dbStore,
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Move composite store implementation into store.go to match repo conventions.

This introduces a store implementation in composite_store.go, but the repo guideline places store interfaces/implementations in store.go. Please relocate the composite store (or consolidate into store.go) to stay consistent.

As per coding guidelines backend/internal/**/store.go: Define store interfaces (e.g., xStoreInterface) and implementations (e.g., xStore struct) in store.go.

🤖 Prompt for AI Agents
In `@backend/internal/application/composite_store.go` around lines 27 - 42, Move
the composite store implementation from composite_store.go into the module's
store.go to follow repo conventions: relocate the compositeApplicationStore type
and its constructor newCompositeApplicationStore (including fileStore and
dbStore fields) into store.go alongside the applicationStoreInterface and other
store implementations; ensure imports and package declarations are adjusted, and
remove the redundant composite_store.go file or export no duplicate symbols so
only the single implementation in store.go remains.

Comment on lines +52 to +67
// GetApplicationList retrieves applications from both stores.
// Note: Application list does not support pagination at the API level, so we don't apply pagination here.
// However, we still apply the 1000-record limit in composite mode to prevent memory exhaustion.
func (c *compositeApplicationStore) GetApplicationList() ([]model.BasicApplicationDTO, error) {
// Use the helper to fetch, merge, and check limits
// Since application list doesn't support pagination, we use limit=MaxInt and offset=0
apps, limitExceeded, err := declarativeresource.CompositeMergeListHelperWithLimit(
func() (int, error) { return c.dbStore.GetTotalApplicationCount() },
func() (int, error) { return c.fileStore.GetTotalApplicationCount() },
func(limit int) ([]model.BasicApplicationDTO, error) { return c.dbStore.GetApplicationList() },
func(limit int) ([]model.BasicApplicationDTO, error) { return c.fileStore.GetApplicationList() },
mergeAndDeduplicateApplications,
100, // Setting limit to 100 as pagination is not supported at API level.
0, // offset 0 - start from beginning
serverconst.MaxCompositeStoreRecords,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

List helper limit set to 100 can truncate results in a non-paginated API.

The API doesn’t paginate, but passing limit=100 risks returning only 100 items even when the total is below the composite cap. Use the composite cap (or MaxInt) as the limit and rely on maxRecords to protect memory.

🐛 Proposed fix
-        100, // Setting limit to 100 as pagination is not supported at API level.
+        serverconst.MaxCompositeStoreRecords, // return full list up to composite cap
📝 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
// GetApplicationList retrieves applications from both stores.
// Note: Application list does not support pagination at the API level, so we don't apply pagination here.
// However, we still apply the 1000-record limit in composite mode to prevent memory exhaustion.
func (c *compositeApplicationStore) GetApplicationList() ([]model.BasicApplicationDTO, error) {
// Use the helper to fetch, merge, and check limits
// Since application list doesn't support pagination, we use limit=MaxInt and offset=0
apps, limitExceeded, err := declarativeresource.CompositeMergeListHelperWithLimit(
func() (int, error) { return c.dbStore.GetTotalApplicationCount() },
func() (int, error) { return c.fileStore.GetTotalApplicationCount() },
func(limit int) ([]model.BasicApplicationDTO, error) { return c.dbStore.GetApplicationList() },
func(limit int) ([]model.BasicApplicationDTO, error) { return c.fileStore.GetApplicationList() },
mergeAndDeduplicateApplications,
100, // Setting limit to 100 as pagination is not supported at API level.
0, // offset 0 - start from beginning
serverconst.MaxCompositeStoreRecords,
)
// GetApplicationList retrieves applications from both stores.
// Note: Application list does not support pagination at the API level, so we don't apply pagination here.
// However, we still apply the 1000-record limit in composite mode to prevent memory exhaustion.
func (c *compositeApplicationStore) GetApplicationList() ([]model.BasicApplicationDTO, error) {
// Use the helper to fetch, merge, and check limits
// Since application list doesn't support pagination, we use limit=MaxInt and offset=0
apps, limitExceeded, err := declarativeresource.CompositeMergeListHelperWithLimit(
func() (int, error) { return c.dbStore.GetTotalApplicationCount() },
func() (int, error) { return c.fileStore.GetTotalApplicationCount() },
func(limit int) ([]model.BasicApplicationDTO, error) { return c.dbStore.GetApplicationList() },
func(limit int) ([]model.BasicApplicationDTO, error) { return c.fileStore.GetApplicationList() },
mergeAndDeduplicateApplications,
serverconst.MaxCompositeStoreRecords, // return full list up to composite cap
0, // offset 0 - start from beginning
serverconst.MaxCompositeStoreRecords,
)
🤖 Prompt for AI Agents
In `@backend/internal/application/composite_store.go` around lines 52 - 67, The
GetApplicationList in compositeApplicationStore passes a hardcoded limit of 100
to declarativeresource.CompositeMergeListHelperWithLimit which can truncate
results for a non-paginated API; change that 100 to use the composite cap (e.g.
serverconst.MaxCompositeStoreRecords or MaxInt) so the helper can return all
records up to the enforced max, keeping mergeAndDeduplicateApplications and the
existing maxRecords parameter unchanged.

Comment on lines 109 to 115
// loadDeclarativeResources loads application resources from declarative files.
// dbStore parameter is used in composite mode to check for duplicate IDs across both stores.
// Pass nil for dbStore when in pure declarative mode.
func loadDeclarativeResources(fileStore applicationStoreInterface, dbStore applicationStoreInterface) error {
// Create a temporary service for validation
appService := newApplicationService(fileStore, nil, nil, nil, nil)

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix newApplicationService argument count to avoid a compile failure.

This call passes 5 args, but the other call site uses 6. Go doesn’t allow overloading, so this will not compile unless the signature changed everywhere.

🐛 Proposed fix
- appService := newApplicationService(fileStore, nil, nil, nil, nil)
+ appService := newApplicationService(fileStore, nil, nil, nil, nil, nil)
📝 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
// loadDeclarativeResources loads application resources from declarative files.
// dbStore parameter is used in composite mode to check for duplicate IDs across both stores.
// Pass nil for dbStore when in pure declarative mode.
func loadDeclarativeResources(fileStore applicationStoreInterface, dbStore applicationStoreInterface) error {
// Create a temporary service for validation
appService := newApplicationService(fileStore, nil, nil, nil, nil)
// loadDeclarativeResources loads application resources from declarative files.
// dbStore parameter is used in composite mode to check for duplicate IDs across both stores.
// Pass nil for dbStore when in pure declarative mode.
func loadDeclarativeResources(fileStore applicationStoreInterface, dbStore applicationStoreInterface) error {
// Create a temporary service for validation
appService := newApplicationService(fileStore, nil, nil, nil, nil, nil)
🤖 Prompt for AI Agents
In `@backend/internal/application/declarative_resource.go` around lines 109 - 115,
The call to newApplicationService in loadDeclarativeResources uses 5 arguments
but the function requires 6 (causing a compile error); update this invocation in
loadDeclarativeResources to pass the same sixth parameter used at the other call
site (or add a nil placeholder if the service doesn’t need a real value in this
context) so the argument count matches newApplicationService’s signature.

Comment on lines +224 to +236
// Check for duplicate ID in the file store
exists, err := fileStore.IsApplicationExists(app.ID)
if err == nil && exists {
return fmt.Errorf("duplicate application ID '%s': "+
"an application with this ID already exists in declarative resources", app.ID)
}

// COMPOSITE MODE: Check for duplicate ID in the database store
if dbStore != nil {
if exists, err := dbStore.IsApplicationExists(app.ID); err == nil && exists {
return fmt.Errorf("duplicate application ID '%s': "+
"an application with this ID already exists in the database store", app.ID)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Propagate store lookup errors during declarative validation.

Errors from IsApplicationExists are ignored, which can silently skip duplicate detection or mask IO failures. Return the error instead.

🛡️ Suggested fix
- exists, err := fileStore.IsApplicationExists(app.ID)
- if err == nil && exists {
+ exists, err := fileStore.IsApplicationExists(app.ID)
+ if err != nil {
+     return fmt.Errorf("failed to check declarative store for ID '%s': %w", app.ID, err)
+ }
+ if exists {
     return fmt.Errorf("duplicate application ID '%s': "+
         "an application with this ID already exists in declarative resources", app.ID)
 }
...
 if dbStore != nil {
-    if exists, err := dbStore.IsApplicationExists(app.ID); err == nil && exists {
+    exists, err := dbStore.IsApplicationExists(app.ID)
+    if err != nil {
+        return fmt.Errorf("failed to check database store for ID '%s': %w", app.ID, err)
+    }
+    if exists {
         return fmt.Errorf("duplicate application ID '%s': "+
             "an application with this ID already exists in the database store", app.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
// Check for duplicate ID in the file store
exists, err := fileStore.IsApplicationExists(app.ID)
if err == nil && exists {
return fmt.Errorf("duplicate application ID '%s': "+
"an application with this ID already exists in declarative resources", app.ID)
}
// COMPOSITE MODE: Check for duplicate ID in the database store
if dbStore != nil {
if exists, err := dbStore.IsApplicationExists(app.ID); err == nil && exists {
return fmt.Errorf("duplicate application ID '%s': "+
"an application with this ID already exists in the database store", app.ID)
}
// Check for duplicate ID in the file store
exists, err := fileStore.IsApplicationExists(app.ID)
if err != nil {
return fmt.Errorf("failed to check declarative store for ID '%s': %w", app.ID, err)
}
if exists {
return fmt.Errorf("duplicate application ID '%s': "+
"an application with this ID already exists in declarative resources", app.ID)
}
// COMPOSITE MODE: Check for duplicate ID in the database store
if dbStore != nil {
exists, err := dbStore.IsApplicationExists(app.ID)
if err != nil {
return fmt.Errorf("failed to check database store for ID '%s': %w", app.ID, err)
}
if exists {
return fmt.Errorf("duplicate application ID '%s': "+
"an application with this ID already exists in the database store", app.ID)
}
}
🤖 Prompt for AI Agents
In `@backend/internal/application/declarative_resource.go` around lines 224 - 236,
The code currently swallows errors from fileStore.IsApplicationExists and
dbStore.IsApplicationExists; update the checks in the declarative validation
(around the IsApplicationExists calls) to return the underlying error instead of
ignoring it—i.e., if err != nil return fmt.Errorf("failed to check application
existence: %w", err) (or similar) for both fileStore.IsApplicationExists(app.ID)
and dbStore.IsApplicationExists(app.ID), while still returning the duplicate-ID
fmt.Errorf when exists == true; ensure you wrap the original error to preserve
context.

Comment on lines +28 to +32
// Store-level errors for application operations.
var (
// ErrResultLimitExceededInCompositeMode is returned when the result limit is exceeded in composite mode.
ErrResultLimitExceededInCompositeMode = errors.New("result limit exceeded in composite mode")
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Make the composite-mode sentinel error unexported.
This is a store-internal error and shouldn’t be exported from a constants file; it can stay package-private with a lowercase name and the references updated accordingly.

🔧 Suggested change
-	ErrResultLimitExceededInCompositeMode = errors.New("result limit exceeded in composite mode")
+	errResultLimitExceededInCompositeMode = errors.New("result limit exceeded in composite mode")

As per coding guidelines: "backend/internal/**/*constants.go: Keep internal constants such as database queries, error codes, and other implementation details unexported (private)".

🤖 Prompt for AI Agents
In `@backend/internal/application/error_constants.go` around lines 28 - 32, Rename
the exported sentinel error ErrResultLimitExceededInCompositeMode to a
package-private name (e.g., errResultLimitExceededInCompositeMode) and update
all references to this symbol throughout the package to use the new lowercase
identifier; ensure the variable still uses errors.New("result limit exceeded in
composite mode") and that any callers within the package import no changes
beyond the identifier rename so the error remains internal to the package.

Comment on lines +236 to +241
// ErrorCannotModifyDeclarativeResource is the error returned when trying to modify a declarative resource.
ErrorCannotModifyDeclarativeResource = serviceerror.ServiceError{
Type: serviceerror.ClientErrorType,
Code: "APP-1028",
Error: "Cannot modify declarative resource",
ErrorDescription: "The application is declarative and cannot be modified or deleted",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix duplicate error code (APP-1028).
ErrorWhileRetrievingFlowDefinition already uses APP-1028, so this new error should have a unique code to avoid client-side ambiguity.

🔧 Suggested change
-		Code:             "APP-1028",
+		Code:             "APP-1030",
📝 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
// ErrorCannotModifyDeclarativeResource is the error returned when trying to modify a declarative resource.
ErrorCannotModifyDeclarativeResource = serviceerror.ServiceError{
Type: serviceerror.ClientErrorType,
Code: "APP-1028",
Error: "Cannot modify declarative resource",
ErrorDescription: "The application is declarative and cannot be modified or deleted",
// ErrorCannotModifyDeclarativeResource is the error returned when trying to modify a declarative resource.
ErrorCannotModifyDeclarativeResource = serviceerror.ServiceError{
Type: serviceerror.ClientErrorType,
Code: "APP-1030",
Error: "Cannot modify declarative resource",
ErrorDescription: "The application is declarative and cannot be modified or deleted",
🤖 Prompt for AI Agents
In `@backend/internal/application/error_constants.go` around lines 236 - 241, The
ErrorCannotModifyDeclarativeResource constant currently reuses the code
"APP-1028" already assigned to ErrorWhileRetrievingFlowDefinition; update the
ServiceError.Code value for ErrorCannotModifyDeclarativeResource to a unique
code (for example "APP-1029") by editing the Code field in the
serviceerror.ServiceError literal for ErrorCannotModifyDeclarativeResource so it
no longer conflicts with ErrorWhileRetrievingFlowDefinition.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@backend/internal/application/composite_store.go`:
- Around line 163-178: The code mutates input slice elements by setting
dbApps[i].IsReadOnly and fileApps[i].IsReadOnly directly; instead, create a
local copy of each element before changing IsReadOnly and appending to result
(e.g., copy := dbApps[i]; copy.IsReadOnly = false; result = append(result,
copy)), and do the same for fileApps with IsReadOnly = true; apply this change
where dbApps, fileApps, seen, and result are handled so the original caller
slices remain unchanged.
🧹 Nitpick comments (1)
backend/internal/application/composite_store.go (1)

61-62: Fetcher functions ignore the limit parameter.

The fetchers wrap GetApplicationList() which takes no parameters, so the limit int passed by the helper is unused. This is unavoidable given the current interface, but the code could be clearer by adding a comment explaining this design constraint.

📝 Suggested clarifying comment
-		func(limit int) ([]model.BasicApplicationDTO, error) { return c.dbStore.GetApplicationList() },
-		func(limit int) ([]model.BasicApplicationDTO, error) { return c.fileStore.GetApplicationList() },
+		// Note: limit param is unused since GetApplicationList() doesn't support pagination.
+		// The helper's limit only affects final result slicing, not fetch optimization.
+		func(limit int) ([]model.BasicApplicationDTO, error) { return c.dbStore.GetApplicationList() },
+		func(limit int) ([]model.BasicApplicationDTO, error) { return c.fileStore.GetApplicationList() },

Comment on lines +163 to +178
// Add DB apps first (they take precedence) - mark as mutable (isReadOnly=false)
for i := range dbApps {
if !seen[dbApps[i].ID] {
seen[dbApps[i].ID] = true
dbApps[i].IsReadOnly = false
result = append(result, dbApps[i])
}
}

// Add file apps if not already present - mark as immutable (isReadOnly=true)
for i := range fileApps {
if !seen[fileApps[i].ID] {
seen[fileApps[i].ID] = true
fileApps[i].IsReadOnly = true
result = append(result, fileApps[i])
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Input slice elements are mutated.

The function modifies dbApps[i].IsReadOnly and fileApps[i].IsReadOnly directly, which mutates the caller's slices. This could cause unexpected side effects if the caller reuses the input slices.

🛡️ Proposed fix to avoid mutation
 	// Add DB apps first (they take precedence) - mark as mutable (isReadOnly=false)
 	for i := range dbApps {
 		if !seen[dbApps[i].ID] {
 			seen[dbApps[i].ID] = true
-			dbApps[i].IsReadOnly = false
-			result = append(result, dbApps[i])
+			app := dbApps[i]
+			app.IsReadOnly = false
+			result = append(result, app)
 		}
 	}

 	// Add file apps if not already present - mark as immutable (isReadOnly=true)
 	for i := range fileApps {
 		if !seen[fileApps[i].ID] {
 			seen[fileApps[i].ID] = true
-			fileApps[i].IsReadOnly = true
-			result = append(result, fileApps[i])
+			app := fileApps[i]
+			app.IsReadOnly = true
+			result = append(result, app)
 		}
 	}
📝 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
// Add DB apps first (they take precedence) - mark as mutable (isReadOnly=false)
for i := range dbApps {
if !seen[dbApps[i].ID] {
seen[dbApps[i].ID] = true
dbApps[i].IsReadOnly = false
result = append(result, dbApps[i])
}
}
// Add file apps if not already present - mark as immutable (isReadOnly=true)
for i := range fileApps {
if !seen[fileApps[i].ID] {
seen[fileApps[i].ID] = true
fileApps[i].IsReadOnly = true
result = append(result, fileApps[i])
}
// Add DB apps first (they take precedence) - mark as mutable (isReadOnly=false)
for i := range dbApps {
if !seen[dbApps[i].ID] {
seen[dbApps[i].ID] = true
app := dbApps[i]
app.IsReadOnly = false
result = append(result, app)
}
}
// Add file apps if not already present - mark as immutable (isReadOnly=true)
for i := range fileApps {
if !seen[fileApps[i].ID] {
seen[fileApps[i].ID] = true
app := fileApps[i]
app.IsReadOnly = true
result = append(result, app)
}
🤖 Prompt for AI Agents
In `@backend/internal/application/composite_store.go` around lines 163 - 178, The
code mutates input slice elements by setting dbApps[i].IsReadOnly and
fileApps[i].IsReadOnly directly; instead, create a local copy of each element
before changing IsReadOnly and appending to result (e.g., copy := dbApps[i];
copy.IsReadOnly = false; result = append(result, copy)), and do the same for
fileApps with IsReadOnly = true; apply this change where dbApps, fileApps, seen,
and result are handled so the original caller slices remain unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant