Skip to content

[Platform API] Add management operations for Deployment Feature#671

Merged
dushaniw merged 68 commits intowso2:mainfrom
dushaniw:main
Jan 27, 2026
Merged

[Platform API] Add management operations for Deployment Feature#671
dushaniw merged 68 commits intowso2:mainfrom
dushaniw:main

Conversation

@dushaniw
Copy link
Contributor

@dushaniw dushaniw commented Jan 14, 2026

Purpose

This pull request implements a new deployment feature that replaces the old revision-based deployment system with an immutable deployment artifact model. The changes introduce deployment lifecycle management (deploy, redeploy, undeploy, delete) with per-gateway deployment limits and real-time event broadcasting to gateways.

Goals

The design is centered around a new core concept: the Deployment. This PR address the first milestone by adding management operations for deployments in platform-api side.

A Deployment is an immutable, self-contained artifact that represents a specific snapshot of an API, bundled with the gateway-specific configurations it was deployed with. Each time an API is deployed to a gateway, a new Deployment artifact is created.
Key Principles:

  • Each Deployment artifact is a fully self-contained copy of the API definition and its configuration. It has no dependency on its base, meaning a base deployment can be deleted without affecting any children created from it.
  • This model does not prescribe a strict environment hierarchy. A deployment to a "staging" gateway and a "production" gateway are simply two independent deployment events that produce two independent artifacts.

Changes:

- Replaced revision-based API deployments with immutable deployment artifacts that store complete API snapshots
- Added deployment lifecycle endpoints: deploy, redeploy, undeploy, delete, list, and get operations
- Implemented configurable per-gateway deployment limits (default 20) with automatic pruning of oldest undeployed artifacts
- Added gateway event broadcasting for both deployment and undeployment operations
- Removed legacy revision fields from API models, DTOs, and database schema

User stories

  • Deployment creation for API for a gateway
  • Get Deployment List for API (query on gateway and status)
  • Get Deployment by deployment Id for an API
  • Delete Deployment by deployment Id
  • Redeploy already creating deployment by deployment id and api id
  • Undeploy deployment by deployment for an API
  • Broadcast deployment event to GW Controller on deployment creation, redeploy
  • Broadcast undeployment event to GW on deployment undeploy

Documentation

Has Doc Impact

Automation tests

  • Unit tests
    In progress
  • Integration tests
    In Progress

Security checks

Samples

N/A

Related PRs

N/A

Test environment

Postgresql

Summary by CodeRabbit

  • New Features

    • API Deployments: create, redeploy, undeploy, delete, list, retrieve, and download immutable deployment artifacts with metadata and timestamps; new deployment endpoints and schemas.
  • Enhancements

    • Per-gateway deployment limit configurable (default 20) with automatic pruning.
    • Real-time gateway broadcasts for deploy and undeploy; gateways can fetch active deployment and receive explicit 404 when none active.
    • Gateway tokens management added.
  • Removed

    • Legacy revision-based deployment endpoints and revision fields.
  • Bug Fixes

    • Improved deployment error responses and not-found/conflict handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Walkthrough

Replaces revision-based deployments with immutable deployment artifacts: DB and model schema changes, new deployment DTOs/endpoints, repository methods, DeploymentService and DeploymentHandler (deploy/redeploy/undeploy/delete/list/content), undeploy broadcasting, config/error additions, gateway token/schema updates, and server wiring. (30 words)

Changes

Cohort / File(s) Summary
Configuration & Errors
platform-api/src/config/config.go, platform-api/src/internal/constants/error.go
Added Deployments config (MaxPerAPIGateway) on Server and new error variables: ErrDeploymentNotFound, ErrDeploymentNotActive, ErrDeploymentIsDeployed, ErrDeploymentAlreadyActive, ErrBaseDeploymentNotFound, ErrInvalidDeploymentStatus.
Database Schema
platform-api/src/internal/database/schema.sql, .../schema.postgres.sql, .../schema.sqlite.sql
Reworked api_deployments: replaced numeric id with deployment_id PK, added status, base_deployment_id, content, metadata, CHECK on status, removed UNIQUE(api_uuid,gateway_uuid); added indexes; reintroduced gateway_tokens; removed revision columns from apis.
DTOs & OpenAPI
platform-api/src/internal/dto/api.go, platform-api/src/internal/dto/gateway.go, platform-api/src/resources/openapi.yaml
Removed revision fields from API DTOs; added DeployAPIRequest, DeploymentResponse, DeploymentListResponse; removed deploy-revision endpoint; added deploy/deployments endpoints and schemas; APIDeploymentDetails now uses deploymentId/deployedAt.
Models & Events
platform-api/src/internal/model/*.go, platform-api/src/internal/model/gateway_event.go
Dropped API revision fields; redefined APIDeployment as immutable artifact with DeploymentID, Status, BaseDeploymentID, Content, Metadata, CreatedAt; added DeploymentStatus constants; gateway event uses DeploymentID and adds Environment; gateway models gain DeploymentID.
Repository Layer
platform-api/src/internal/repository/api.go, platform-api/src/internal/repository/interfaces.go
Added deployment persistence and queries: CreateDeployment, extended GetDeploymentsByAPIUUID(api,gateway,status), GetDeploymentByID, GetDeploymentContent, UpdateDeploymentStatus, DeleteDeployment, GetActiveDeploymentByGateway, GetOldestUndeployedDeploymentByGateway, CountDeploymentsByAPIAndGateway; metadata JSON (un)marshalling and not-found errors.
Service Layer — API
platform-api/src/internal/service/api.go, platform-api/src/internal/service/gateway_internal.go
Removed revision-related fields/methods (deploy-revision); internal gateway flows now consult active deployments and map inactive deployment to 404; adjusted gateway deployment responses and removed revision initialization.
Service Layer — Deployment (New)
platform-api/src/internal/service/deployment.go
New DeploymentService with Deploy/Redeploy/Undeploy/Delete/Get(s)/GetContent and handle-based variants; enforces per-gateway limits (evicts oldest undeployed), stores immutable artifacts (content/metadata), manages status transitions, ensures API-gateway associations, broadcasts deployment/undeployment events.
Gateway Events
platform-api/src/internal/service/gateway_events.go
Added BroadcastUndeploymentEvent to broadcast undeploy notifications to gateways (mirrors deployment broadcast flow).
Handlers & Routes
platform-api/src/internal/handler/deployment.go, platform-api/src/internal/handler/api.go, platform-api/src/internal/handler/gateway_internal.go
Added DeploymentHandler with endpoints for deployment lifecycle and content; removed DeployAPIRevision handler/route; internal gateway handler fetches active deployment per gateway and returns 404 for inactive.
Server Wiring
platform-api/src/internal/server/server.go
Wired DeploymentService and DeploymentHandler into server initialization and router registration.
Utilities
platform-api/src/internal/utils/api.go
Removed mapping/propagation of revision fields in DTO↔Model conversions, YAML translation, and merge logic.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Handler as DeploymentHandler
    participant Service as DeploymentService
    participant Repo as APIRepository
    participant DB as Database
    participant Events as GatewayEvents

    Client->>Handler: POST /apis/{apiId}/deploy (DeployAPIRequest)
    Handler->>Service: DeployAPI(apiUUID, req, orgUUID)
    Service->>Repo: CountDeploymentsByAPIAndGateway(api,gateway,org)
    Repo->>DB: SELECT COUNT(...)
    DB-->>Repo: count
    Repo-->>Service: count
    alt count >= MaxPerAPIGateway
        Service->>Repo: GetOldestUndeployedDeploymentByGateway(...)
        Repo->>DB: SELECT oldest undeployed
        DB-->>Repo: deployment
        Repo-->>Service: oldest deployment
        Service->>Repo: DeleteDeployment(oldDeploymentID,...)
        Repo->>DB: DELETE ...
        DB-->>Repo: deleted
    end
    Service->>Repo: CreateDeployment(newDeployment)
    Repo->>DB: INSERT deployment (deployment_id, content, metadata, status)
    DB-->>Repo: inserted
    Repo-->>Service: success
    Service->>Repo: GetActiveDeploymentByGateway(api,gateway,org)
    Repo->>DB: SELECT WHERE status='DEPLOYED' LIMIT 1
    DB-->>Repo: previous deployment?
    Repo-->>Service: previous deployment or nil
    alt previous exists
        Service->>Repo: UpdateDeploymentStatus(prevID,'UNDEPLOYED')
        Repo->>DB: UPDATE status
        DB-->>Repo: updated
        Service->>Events: BroadcastUndeploymentEvent(gateway, payload)
        Events->>Events: serialize & broadcast
    end
    Service->>Events: BroadcastDeploymentEvent(gateway, payload)
    Events->>Events: serialize & broadcast
    Service-->>Handler: DeploymentResponse
    Handler-->>Client: 201 Created {deploymentId, status, ...}
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

"I hop with a patch and a tiny drum,
old drafts cleared out—new artifacts come.
Gateways hum, events softly chime,
deployments fixed in careful time.
The rabbit cheers: deploy and rhyme!" 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding management operations for a new Deployment Feature, which is the core focus of this PR.
Description check ✅ Passed The PR description is comprehensive and covers most template sections including Purpose, Goals, User stories, Documentation, and Security checks, though Automation tests and Test environment are marked as in-progress.

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


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.

@dushaniw dushaniw marked this pull request as ready for review January 16, 2026 10:45
Copilot AI review requested due to automatic review settings January 16, 2026 10:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (1)
platform-api/src/internal/service/gateway_internal.go (1)

273-283: Set deployment Content and Status before insert

The api_deployments table requires content as NOT NULL and status must have a valid value. The current code creates an APIDeployment with only ApiID, GatewayID, OrganizationID, and CreatedAt—leaving Content and Status unset. The CreateDeployment method will attempt to insert these empty/null values, causing the insert to fail. Populate Content with the API definition and set Status to a valid value (e.g., "DEPLOYED") before calling CreateDeployment.

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/database/schema.sql`:
- Around line 220-236: The CREATE TABLE for api_deployments has a missing comma
between the table constraints UNIQUE(api_uuid, gateway_uuid, deployment_id,
organization_uuid) and CHECK (status IN (...)), which causes a SQL syntax error;
add a comma after the UNIQUE(...) constraint in the api_deployments table
definition so the UNIQUE constraint and the CHECK constraint are properly
separated and the table (functionally api_deployments) can be created.

In `@platform-api/src/internal/handler/deployment.go`:
- Around line 188-230: The DeleteDeployment handler currently maps service
errors to 500; update error handling after calling
deploymentService.DeleteDeploymentByHandle(apiId, deploymentId, orgId) to map
the service error that indicates an active deployment (e.g.,
constants.ErrDeploymentActive or the specific error returned by your service) to
HTTP 409 with an appropriate utils.NewErrorResponse(409, "Conflict", "<human
message>"), leaving other errors as-is; also return no body for success by using
c.Status(http.StatusNoContent) (or c.JSON(http.StatusNoContent, nil)
consistently) instead of sending a body with 204.
- Around line 256-271: The 500 response currently returns "Deployment not found"
which masks internal errors; in the error branch after calling
h.deploymentService.GetDeploymentByHandle (and after checking
constants.ErrAPINotFound and constants.ErrDeploymentNotFound) change the c.JSON
internal server error body to include the actual error details (e.g.,
err.Error() or a message like "Failed to retrieve deployment") instead of
"Deployment not found" so the response accurately reflects the internal failure
while keeping the existing log.Printf call.
- Around line 314-355: In GetDeploymentContent, the handler currently falls
through after sending error JSON (causing a second response) and sends the wrong
media type; add a return immediately after each c.JSON error response in the
error branch of h.deploymentService.GetDeploymentContentByHandle so the function
exits on error, and change the final c.Data content type from "application/json"
to the correct YAML media type (e.g., "application/x-yaml" or "text/yaml") to
match GenerateAPIDeploymentYAML-produced artifacts.
- Around line 144-186: Handler UndeployDeployment currently sends a 204 with a
JSON null (c.JSON(http.StatusNoContent, nil)) which mismatches the API contract;
update the handler to either return the spec'd 200 with a DeploymentResponse
body (populate and return the deployment via the
deploymentService.UndeployDeploymentByHandle result) or, if you choose to keep
204, replace c.JSON(http.StatusNoContent, nil) with
c.Status(http.StatusNoContent) and ensure no body is sent. Also add explicit
error handling for a "deployment not currently active" error from
UndeployDeploymentByHandle (e.g., constants.ErrDeploymentNotActive or the
service-specific error) and map it to a 400 or 409 response instead of 500; keep
the existing mappings for constants.ErrAPINotFound and
constants.ErrDeploymentNotFound.

In `@platform-api/src/internal/repository/api.go`:
- Around line 1108-1123: The GetDeploymentContent function currently returns a
generic error when the deployment row is missing; change it to return the
package-level sentinel ErrDeploymentNotFound instead of errors.New("deployment
not found") so callers can map to 404s; locate APIRepo.GetDeploymentContent (and
the similar methods around GetDeploymentLogs/GetDeploymentMeta in the same file)
and replace the sql.ErrNoRows branch to return ErrDeploymentNotFound, keeping
other error returns unchanged and ensuring the sentinel is imported/defined in
the package.
- Around line 1327-1332: The LEFT JOIN to api_deployments (alias ad) can match
non-active or older deployments causing duplicates and incorrect is_deployed
flags; change the join to only include active deployments by adding a filter
ad.status = 'DEPLOYED' (and to avoid multiple rows per gateway select the latest
deployment—e.g., join against a subquery that for each gateway_uuid and api_uuid
returns the deployment with max(created_at) or max(deployment_id) before
joining). Update the JOIN condition on ad in the query that references g, aa, ad
(use the existing ON g.uuid = ad.gateway_uuid AND ad.api_uuid = ? plus the
status filter or replace ad with a deduplicated subquery) so is_deployed and
deployed_at reflect the latest deployed artifact only.
- Around line 964-989: In CreateDeployment, json.Marshal(deployment.Metadata)
currently ignores errors and can silently drop invalid metadata; capture the
error when marshaling deployment.Metadata (the code handling metadataJSON inside
CreateDeployment) and return or wrap that error instead of proceeding, ensuring
the INSERT does not run with missing/invalid metadata; update the logic that
sets metadataJSON (and any callers/returns) to handle the marshal error
appropriately and propagate it back to the caller.

In `@platform-api/src/internal/service/deployment.go`:
- Around line 239-244: Replace ad-hoc string errors for missing deployments with
the sentinel error constants.ErrDeploymentNotFound so callers using
errors.Is(...) will map to 404s: locate the branches that check the deployment
variable for nil (e.g., the block that currently returns errors.New("deployment
not found")) and change the return to constants.ErrDeploymentNotFound; do the
same for every other "not found" branch in this file (search for
errors.New("deployment not found") or similar) so all not-found returns
consistently use the ErrDeploymentNotFound sentinel.
- Around line 96-169: The deployment limit check in DeployAPI runs before
undeploying the current active deployment and only deletes one undeployed
artifact, which can exceed s.cfg.Deployments.MaxPerAPIGateway; also failures
from s.apiRepo.UpdateDeploymentStatus(existingDeployment.DeploymentID, ...,
"UNDEPLOYED", ...) are ignored—make them fatal. Fix by first attempting to
undeploy any existing active deployment via GetActiveDeploymentByGateway and
calling UpdateDeploymentStatus, returning an error if that call fails; then call
CountDeploymentsByAPIAndGateway and, while apiDeploymentCount >=
s.cfg.Deployments.MaxPerAPIGateway, fetch and delete the oldest undeployed
deployment using GetOldestUndeployedDeploymentByGateway and DeleteDeployment
(loop until under the limit), returning errors on any repo failures; apply the
same changes in RedeployDeployment.

In `@platform-api/src/resources/openapi.yaml`:
- Around line 946-973: The OpenAPI operation UndeployDeployment currently
declares a 200 response with schema DeploymentResponse but the implementation
returns 204 No Content; update the spec to match the implementation by replacing
the '200' response under operationId UndeployDeployment with a '204' response
that has description like "Deployment undeployed successfully" and no response
body (remove or stop referencing components/schemas/DeploymentResponse), or
alternatively change the handler to return 200 with the DeploymentResponse
payload—pick one approach and make the operationId UndeployDeployment, its
responses block, and any references to components/schemas/DeploymentResponse
consistent.
- Around line 910-945: The OpenAPI spec defines a required requestBody using the
RedeployRequest schema for the RedeployDeployment operation, but the
handler/service function redeploy currently expects no body; fix by either
making the requestBody optional in the spec (remove required: true or delete the
requestBody block for /apis/{apiId}/deployments/{deploymentId}/redeploy) or by
updating the redeploy implementation to accept and validate RedeployRequest
(consume gatewayId/metadata) and forward it to the redeploy service; ensure the
handler name (RedeployDeployment) and the service method redeploy accept the
same payload shape and update the components/schemas/RedeployRequest usage
accordingly, plus add input validation and tests for both paths.
- Around line 975-996: The OpenAPI response for operationId GetDeploymentContent
(path /apis/{apiId}/deployments/{deploymentId}/content) is incorrect: the
implementation returns raw YAML bytes but the spec declares application/json
with an object schema; update the spec to match the implementation by changing
the 200 response content type to application/x-yaml (or
application/octet-stream) and set the schema to a binary/string payload (e.g.,
type: string, format: binary or type: string with description "raw YAML bytes"),
so clients correctly interpret the raw YAML bytes instead of expecting JSON.
🧹 Nitpick comments (4)
platform-api/src/internal/service/gateway_events.go (1)

138-209: Extract shared broadcast helper to prevent drift

BroadcastUndeploymentEvent duplicates the deployment broadcast flow (marshal, size check, connection loop, stats/logging). Consider a shared helper that takes event type/payload so future changes stay consistent across both paths.

platform-api/src/internal/dto/api.go (1)

165-188: Validate DeployAPIRequest.Base values

Base is free-form but only "current" or a deployment ID are valid. Consider enforcing this via constants/validation to fail fast on bad requests and reduce downstream ambiguity.

platform-api/src/internal/service/gateway_internal.go (1)

286-289: Avoid returning legacy deploymentId=0

With deployment IDs now strings, returning DeploymentId: 0 can mislead clients. Consider deprecating this field or updating the response type to expose the new deployment ID.

platform-api/src/internal/repository/api.go (1)

996-1065: Avoid fetching content blobs in list queries.
GetDeploymentsByAPIUUID selects content for every row, but the list response doesn’t expose it. This can be a large, unnecessary payload. Consider excluding content (or making it opt-in).

♻️ Example change (apply to all query branches)
-SELECT deployment_id, api_uuid, organization_uuid, gateway_uuid, status, base_deployment_id, content, metadata, created_at
+SELECT deployment_id, api_uuid, organization_uuid, gateway_uuid, status, base_deployment_id, metadata, created_at
...
-err := rows.Scan(&deployment.DeploymentID, &deployment.ApiID, &deployment.OrganizationID,
-	&deployment.GatewayID, &deployment.Status, &baseDeploymentID, &deployment.Content, &metadataJSON, &deployment.CreatedAt)
+err := rows.Scan(&deployment.DeploymentID, &deployment.ApiID, &deployment.OrganizationID,
+	&deployment.GatewayID, &deployment.Status, &baseDeploymentID, &metadataJSON, &deployment.CreatedAt)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68f4195 and 0ef8547.

📒 Files selected for processing (18)
  • platform-api/src/config/config.go
  • platform-api/src/internal/constants/error.go
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/dto/api.go
  • platform-api/src/internal/handler/api.go
  • platform-api/src/internal/handler/deployment.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/model/api.go
  • platform-api/src/internal/model/gateway_event.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/api.go
  • platform-api/src/internal/service/deployment.go
  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/internal/utils/api.go
  • platform-api/src/resources/openapi.yaml
💤 Files with no reviewable changes (3)
  • platform-api/src/internal/handler/api.go
  • platform-api/src/internal/utils/api.go
  • platform-api/src/internal/service/api.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-11-08T13:06:22.133Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 110
File: platform-api/src/internal/service/api.go:432-476
Timestamp: 2025-11-08T13:06:22.133Z
Learning: In the platform-api Go codebase (file: platform-api/src/internal/service/api.go), the DeployAPIRevision method is designed to automatically create API-gateway associations during deployment if they don't already exist. There is no requirement that associations must pre-exist before deployment; the system checks for existing associations and creates them on-the-fly as needed.

Applied to files:

  • platform-api/src/internal/constants/error.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/model/gateway_event.go
  • platform-api/src/internal/dto/api.go
  • platform-api/src/internal/model/api.go
  • platform-api/src/internal/handler/deployment.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/resources/openapi.yaml
  • platform-api/src/internal/service/deployment.go
📚 Learning: 2025-12-06T11:57:08.456Z
Learnt from: BLasan
Repo: wso2/api-platform PR: 140
File: gateway/gateway-controller/pkg/utils/api_deployment.go:88-105
Timestamp: 2025-12-06T11:57:08.456Z
Learning: In the gateway-controller Go codebase (file: gateway/gateway-controller/pkg/utils/api_deployment.go), only the async/websub API kind is currently supported. async/websocket and async/sse API kinds are not yet implemented.

Applied to files:

  • platform-api/src/internal/constants/error.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/internal/service/deployment.go
📚 Learning: 2026-01-15T11:14:32.797Z
Learnt from: VirajSalaka
Repo: wso2/api-platform PR: 686
File: platform-api/src/internal/service/api.go:920-922
Timestamp: 2026-01-15T11:14:32.797Z
Learning: In the platform-api service (file: platform-api/src/internal/service/api.go), API handles (the ID field) are immutable identifiers used as references and should not be updated after API creation. They serve as stable, user-facing identifiers for the API lifecycle.

Applied to files:

  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/model/gateway_event.go
  • platform-api/src/internal/dto/api.go
  • platform-api/src/internal/model/api.go
  • platform-api/src/internal/service/deployment.go
📚 Learning: 2026-01-09T05:58:05.318Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 644
File: gateway/gateway-controller/pkg/storage/memory.go:484-503
Timestamp: 2026-01-09T05:58:05.318Z
Learning: In gateway/gateway-controller/pkg/storage/memory.go, during API key rotation via the StoreAPIKey method, the API key ID is never modified. When an existing API key is found by name (existingKeyID != ""), the incoming apiKey.ID is guaranteed to be the same as existingKeyID, so no ID mismatch can occur during updates.

Applied to files:

  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/service/gateway_internal.go
📚 Learning: 2026-01-09T05:57:36.635Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 644
File: gateway/gateway-controller/pkg/storage/apikey_store.go:219-222
Timestamp: 2026-01-09T05:57:36.635Z
Learning: In gateway/gateway-controller/pkg/storage/apikey_store.go, during API key rotation, the API key ID and name remain constant—only the API key value changes. Therefore, when addToAPIMapping finds an existing key by name, the existingKeyID will always equal the incoming apiKey.ID.

Applied to files:

  • platform-api/src/internal/handler/gateway_internal.go
📚 Learning: 2025-12-18T08:33:51.340Z
Learnt from: O-sura
Repo: wso2/api-platform PR: 485
File: gateway/policy-engine/internal/analytics/dto/unwrappedEvent.go:21-90
Timestamp: 2025-12-18T08:33:51.340Z
Learning: In gateway/policy-engine/internal/analytics/dto/unwrappedEvent.go, the JSON tag casing differences between DefaultFaultEvent (correlationID, applicationID, organizationID, environmentID, regionID with uppercase 'ID') and DefaultResponseEvent (correlationId, applicationId, organizationId, environmentId, regionId with lowercase 'Id') are intentional to maintain consistency with respective event type conventions and config file property standards.

Applied to files:

  • platform-api/src/internal/model/gateway_event.go
🧬 Code graph analysis (8)
platform-api/src/internal/handler/gateway_internal.go (2)
platform-api/src/internal/utils/error.go (1)
  • NewErrorResponse (28-37)
platform-api/src/internal/constants/error.go (1)
  • ErrDeploymentNotActive (69-69)
platform-api/src/internal/service/gateway_events.go (2)
platform-api/src/internal/model/gateway_event.go (1)
  • APIUndeploymentEvent (66-75)
platform-api/src/internal/dto/gateway_event.go (1)
  • GatewayEventDTO (21-33)
platform-api/src/internal/server/server.go (2)
platform-api/src/internal/service/deployment.go (1)
  • NewDeploymentService (48-66)
platform-api/src/internal/handler/deployment.go (1)
  • NewDeploymentHandler (38-42)
platform-api/src/internal/repository/api.go (1)
platform-api/src/internal/model/api.go (2)
  • APIDeployment (243-253)
  • APIDeployment (256-258)
platform-api/src/internal/dto/api.go (1)
platform-api/src/internal/dto/common.go (1)
  • Pagination (23-27)
platform-api/src/internal/handler/deployment.go (4)
platform-api/src/internal/middleware/auth.go (1)
  • GetOrganizationFromContext (169-176)
platform-api/src/internal/utils/error.go (1)
  • NewErrorResponse (28-37)
platform-api/src/internal/dto/api.go (1)
  • DeployAPIRequest (166-170)
platform-api/src/internal/constants/error.go (3)
  • ErrAPINotFound (47-47)
  • ErrGatewayNotFound (62-62)
  • ErrDeploymentNotFound (68-68)
platform-api/src/internal/repository/interfaces.go (1)
platform-api/src/internal/model/api.go (2)
  • APIDeployment (243-253)
  • APIDeployment (256-258)
platform-api/src/internal/service/gateway_internal.go (1)
platform-api/src/internal/constants/error.go (1)
  • ErrDeploymentNotActive (69-69)
🔇 Additional comments (24)
platform-api/src/internal/constants/error.go (1)

67-70: LGTM — deployment-specific errors are clearly defined.
Names/messages fit the existing error taxonomy and support new deployment flows.

platform-api/src/config/config.go (2)

48-49: Deployment configuration is wired into the server config.


111-114: Default per-API gateway limit looks reasonable.

platform-api/src/internal/model/gateway_event.go (1)

48-62: Deployment event payload aligns with the new deployment model.

platform-api/src/internal/handler/gateway_internal.go (2)

20-32: Import update looks fine.


124-139: Active-deployment lookup and 404 mapping are appropriate.

platform-api/src/internal/server/server.go (1)

104-105: Deployment service/handler wiring and route registration look correct.

Also applies to: 115-116, 146-146

platform-api/src/internal/service/gateway_internal.go (1)

98-116: LGTM: active deployment lookup

Returning ErrDeploymentNotActive on nil and using stored content keeps gateway retrieval clear and consistent.

platform-api/src/internal/model/api.go (1)

242-252: Metadata persistence is already handled correctly

The repository layer in platform-api/src/internal/repository/api.go explicitly marshals Metadata to JSON string before insert and unmarshals it back to the map on retrieval. No runtime scan/insert failures will occur since JSON handling is implemented in the persistence layer, not relying on database/sql automatic scanning.

Likely an incorrect or invalid review comment.

platform-api/src/internal/handler/deployment.go (5)

34-42: Constructor wiring is clean.
Straightforward dependency injection.


44-98: Deploy handler validation and error mapping look solid.


277-312: List handler looks good.


357-368: Route registration is consistent.


100-142: Align redeploy handler with the published contract.
OpenAPI defines a required RedeployRequest (gatewayId/metadata), but this handler ignores the body and the service signature doesn't accept it; clients will send required data that gets dropped. Either remove/make the requestBody optional in the spec or bind and plumb it through. Also map the “deployment is already active” state to 400/409 instead of returning 500.

⛔ Skipped due to learnings
Learnt from: thivindu
Repo: wso2/api-platform PR: 110
File: platform-api/src/internal/service/api.go:432-476
Timestamp: 2025-11-08T13:06:22.133Z
Learning: In the platform-api Go codebase (file: platform-api/src/internal/service/api.go), the DeployAPIRevision method is designed to automatically create API-gateway associations during deployment if they don't already exist. There is no requirement that associations must pre-exist before deployment; the system checks for existing associations and creates them on-the-fly as needed.
platform-api/src/internal/repository/interfaces.go (1)

57-67: Deployment repository surface looks coherent.

platform-api/src/internal/repository/api.go (4)

64-75: Column list updates are consistent across CRUD queries.

Also applies to: 123-135, 179-200, 229-265, 365-374


1070-1106: GetDeploymentByID mapping and null handling look good.


1171-1248: Active/oldest deployment lookups are straightforward.


1408-1422: Deployment count helper looks good.

platform-api/src/resources/openapi.yaml (3)

792-909: Deployment endpoints for create/list/get/delete are well documented.


3675-3693: Deployment request/response schemas look consistent.

Also applies to: 3708-3768


3971-4010: New deployment parameters and tag are clear.

platform-api/src/internal/service/deployment.go (2)

36-66: Service wiring looks good.


360-400: List/association helpers and handle-based variants look good.

Also applies to: 454-480, 482-583

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
platform-api/src/internal/service/gateway_internal.go (1)

273-281: Add Status and Content fields to deployment record before insert.

The APIDeployment creation at lines 273–281 omits Status and Content, both of which are NOT NULL in the schema with no repository-side defaults. The content column has no default value, so the INSERT will fail. Even if it succeeded, GetActiveDeploymentByGateway expects to read deployment.Content and would return empty data.

Compare with the proper pattern in deployment.go (lines 187–195), which sets Status: "DEPLOYED" and populates Content before calling CreateDeployment. Follow that same approach here:

Suggested approach
 	// Create deployment record
 	deployment := &model.APIDeployment{
+		Status:         "DEPLOYED",
+		Content:        []byte(notification.Configuration),  // or serialize as needed
 		ApiID:          apiUUID,
 		GatewayID:      gatewayID,
 		OrganizationID: orgID,
 		CreatedAt:      now,
 	}
🤖 Fix all issues with AI agents
In `@platform-api/src/internal/dto/api.go`:
- Around line 172-188: Update the DTOs to match the OpenAPI contract: add an
OrganizationID string field to DeploymentResponse (json:"organizationId"
yaml:"organizationId"), and change DeploymentListResponse to expose Deployments
[]*DeploymentResponse `json:"deployments" yaml:"deployments"` instead of
Count/List/Pagination (i.e., remove Count and Pagination and rename List to
Deployments). Also update any code that constructs or consumes
DeploymentResponse and DeploymentListResponse (handlers/serializers) to populate
OrganizationID and to produce the Deployments slice (and stop expecting
Count/Pagination).

In `@platform-api/src/internal/service/deployment.go`:
- Around line 201-204: The code currently swallows failures from
s.ensureAPIGatewayAssociation(apiUUID, req.GatewayID, orgUUID) by logging a
warning; instead treat association failures as fatal: propagate the error (or
wrap it with context) and return it from the enclosing function so the
deployment is aborted/rolled back rather than reported as successful. Update the
surrounding control flow to return the error from the caller (or trigger the
existing rollback path) and include contextual information (apiUUID,
req.GatewayID, orgUUID) in the returned/wrapped error to aid debugging.
- Around line 575-583: GetDeploymentContentByHandle builds apiUUID via
getAPIUUIDByHandle but then calls GetDeploymentContent with the arguments
swapped; change the call in GetDeploymentContentByHandle to pass deploymentID,
apiUUID, orgUUID (instead of apiUUID, deploymentID, orgUUID) so
GetDeploymentContent receives (deploymentID, apiUUID, orgUUID) in the correct
order.
♻️ Duplicate comments (13)
platform-api/src/internal/database/schema.sql (1)

234-235: Add missing comma between UNIQUE and CHECK constraints.

SQLite requires comma-separated table constraints. This will cause a syntax error when creating the table.

Fix
-    UNIQUE(api_uuid, gateway_uuid, deployment_id, organization_uuid)
-    CHECK (status IN ('DEPLOYED', 'UNDEPLOYED'))
+    UNIQUE(api_uuid, gateway_uuid, deployment_id, organization_uuid),
+    CHECK (status IN ('DEPLOYED', 'UNDEPLOYED'))
platform-api/src/internal/handler/deployment.go (4)

168-185: Align undeploy response and error mapping with the contract.
Still returns 204 with a JSON body and doesn’t map “not active” to a 4xx/409.


212-229: Return conflict for active deployments and avoid body on 204.
Delete should map “active deployment” errors to 409 and use c.Status(204) if keeping No Content.


256-271: Don’t mask internal errors as “not found.”
The 500 path currently returns “Deployment not found,” which is misleading.


338-355: Return after errors and use the correct content type.
The handler falls through after error JSON and serves YAML as JSON.

platform-api/src/resources/openapi.yaml (3)

910-930: Redeploy request body doesn’t match implementation.
Spec requires RedeployRequest, but redeploy currently accepts no body. Make the requestBody optional/remove it or update the handler/service to consume it.


946-966: Undeploy response mismatch.
Spec declares 200 + DeploymentResponse, but the implementation returns 204 No Content. Align the contract.


975-995: Deployment content media type/schema mismatch.
Implementation returns raw YAML bytes but spec declares JSON object. Update to application/x-yaml (or binary) and a string/binary schema.

platform-api/src/internal/repository/api.go (3)

984-987: Handle metadata marshal failures.
json.Marshal errors are ignored, so invalid metadata is silently dropped. Return the error to avoid storing corrupted/empty metadata.

💡 Suggested fix
 	var metadataJSON string
 	if deployment.Metadata != nil {
-		metadataBytes, _ := json.Marshal(deployment.Metadata)
-		metadataJSON = string(metadataBytes)
+		metadataBytes, mErr := json.Marshal(deployment.Metadata)
+		if mErr != nil {
+			return fmt.Errorf("failed to marshal deployment metadata: %w", mErr)
+		}
+		metadataJSON = string(metadataBytes)
 	}

1114-1120: Use ErrDeploymentNotFound for not-found cases.
String errors prevent callers from mapping to 404. Return the sentinel consistently.

Also applies to: 1138-1145, 1159-1166


1327-1332: Filter deployment join to active deployments.
Joining all deployments can duplicate rows and mark is_deployed true even when only undeployed artifacts exist. Filter by status='DEPLOYED' (and ideally select latest).

platform-api/src/internal/service/deployment.go (2)

96-113: Enforce deployment limits after undeploying and fail fast on undeploy errors.
Current flow checks limits before undeploy and logs undeploy failures; this can exceed MaxPerAPIGateway or leave multiple active deployments. Make undeploy failures fatal and trim until under the limit after the undeploy step.

Also applies to: 164-168, 252-256


239-241: Use ErrDeploymentNotFound for missing deployments.
Handlers map this sentinel to 404; string errors will surface as 500s.

Also applies to: 304-306, 345-347, 418-420, 441-443

🧹 Nitpick comments (2)
platform-api/src/internal/service/gateway_events.go (1)

137-209: Consider extracting common broadcast logic to reduce duplication.

The BroadcastUndeploymentEvent method duplicates nearly all of BroadcastDeploymentEvent (correlation ID generation, serialization, size validation, connection lookup, broadcast loop, stats tracking). Consider extracting a generic helper:

func (s *GatewayEventsService) broadcastEvent(gatewayID string, eventType string, payload interface{}) error

This would reduce maintenance burden and ensure consistent behavior across event types.

platform-api/src/internal/database/schema.sql (1)

220-236: Consider adding indexes for common deployment query patterns.

The api_deployments table lacks indexes for common queries. Based on the deployment service operations mentioned in the AI summary, consider adding:

CREATE INDEX IF NOT EXISTS idx_api_deployments_api ON api_deployments(api_uuid);
CREATE INDEX IF NOT EXISTS idx_api_deployments_gateway_status ON api_deployments(gateway_uuid, status);
CREATE INDEX IF NOT EXISTS idx_api_deployments_org ON api_deployments(organization_uuid);

This would optimize queries for fetching deployments by API, finding active deployments by gateway, and listing deployments by organization.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68f4195 and 0ef8547.

📒 Files selected for processing (18)
  • platform-api/src/config/config.go
  • platform-api/src/internal/constants/error.go
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/dto/api.go
  • platform-api/src/internal/handler/api.go
  • platform-api/src/internal/handler/deployment.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/model/api.go
  • platform-api/src/internal/model/gateway_event.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/api.go
  • platform-api/src/internal/service/deployment.go
  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/internal/utils/api.go
  • platform-api/src/resources/openapi.yaml
💤 Files with no reviewable changes (3)
  • platform-api/src/internal/service/api.go
  • platform-api/src/internal/utils/api.go
  • platform-api/src/internal/handler/api.go
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: thivindu
Repo: wso2/api-platform PR: 110
File: platform-api/src/internal/service/api.go:432-476
Timestamp: 2025-11-08T13:06:22.133Z
Learning: In the platform-api Go codebase (file: platform-api/src/internal/service/api.go), the DeployAPIRevision method is designed to automatically create API-gateway associations during deployment if they don't already exist. There is no requirement that associations must pre-exist before deployment; the system checks for existing associations and creates them on-the-fly as needed.
📚 Learning: 2025-11-08T13:06:22.133Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 110
File: platform-api/src/internal/service/api.go:432-476
Timestamp: 2025-11-08T13:06:22.133Z
Learning: In the platform-api Go codebase (file: platform-api/src/internal/service/api.go), the DeployAPIRevision method is designed to automatically create API-gateway associations during deployment if they don't already exist. There is no requirement that associations must pre-exist before deployment; the system checks for existing associations and creates them on-the-fly as needed.

Applied to files:

  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/config/config.go
  • platform-api/src/internal/handler/deployment.go
  • platform-api/src/internal/model/gateway_event.go
  • platform-api/src/internal/database/schema.sql
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/internal/dto/api.go
  • platform-api/src/internal/model/api.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/constants/error.go
  • platform-api/src/resources/openapi.yaml
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/service/deployment.go
📚 Learning: 2025-12-06T11:57:08.456Z
Learnt from: BLasan
Repo: wso2/api-platform PR: 140
File: gateway/gateway-controller/pkg/utils/api_deployment.go:88-105
Timestamp: 2025-12-06T11:57:08.456Z
Learning: In the gateway-controller Go codebase (file: gateway/gateway-controller/pkg/utils/api_deployment.go), only the async/websub API kind is currently supported. async/websocket and async/sse API kinds are not yet implemented.

Applied to files:

  • platform-api/src/internal/service/gateway_events.go
  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/server/server.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/constants/error.go
  • platform-api/src/internal/repository/interfaces.go
  • platform-api/src/internal/service/deployment.go
📚 Learning: 2026-01-15T11:14:32.797Z
Learnt from: VirajSalaka
Repo: wso2/api-platform PR: 686
File: platform-api/src/internal/service/api.go:920-922
Timestamp: 2026-01-15T11:14:32.797Z
Learning: In the platform-api service (file: platform-api/src/internal/service/api.go), API handles (the ID field) are immutable identifiers used as references and should not be updated after API creation. They serve as stable, user-facing identifiers for the API lifecycle.

Applied to files:

  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/handler/deployment.go
  • platform-api/src/internal/model/gateway_event.go
  • platform-api/src/internal/service/gateway_internal.go
  • platform-api/src/internal/dto/api.go
  • platform-api/src/internal/model/api.go
  • platform-api/src/internal/repository/api.go
  • platform-api/src/internal/service/deployment.go
📚 Learning: 2026-01-09T05:58:05.318Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 644
File: gateway/gateway-controller/pkg/storage/memory.go:484-503
Timestamp: 2026-01-09T05:58:05.318Z
Learning: In gateway/gateway-controller/pkg/storage/memory.go, during API key rotation via the StoreAPIKey method, the API key ID is never modified. When an existing API key is found by name (existingKeyID != ""), the incoming apiKey.ID is guaranteed to be the same as existingKeyID, so no ID mismatch can occur during updates.

Applied to files:

  • platform-api/src/internal/handler/gateway_internal.go
  • platform-api/src/internal/service/gateway_internal.go
📚 Learning: 2026-01-09T05:57:36.635Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 644
File: gateway/gateway-controller/pkg/storage/apikey_store.go:219-222
Timestamp: 2026-01-09T05:57:36.635Z
Learning: In gateway/gateway-controller/pkg/storage/apikey_store.go, during API key rotation, the API key ID and name remain constant—only the API key value changes. Therefore, when addToAPIMapping finds an existing key by name, the existingKeyID will always equal the incoming apiKey.ID.

Applied to files:

  • platform-api/src/internal/handler/gateway_internal.go
📚 Learning: 2025-12-18T08:33:51.340Z
Learnt from: O-sura
Repo: wso2/api-platform PR: 485
File: gateway/policy-engine/internal/analytics/dto/unwrappedEvent.go:21-90
Timestamp: 2025-12-18T08:33:51.340Z
Learning: In gateway/policy-engine/internal/analytics/dto/unwrappedEvent.go, the JSON tag casing differences between DefaultFaultEvent (correlationID, applicationID, organizationID, environmentID, regionID with uppercase 'ID') and DefaultResponseEvent (correlationId, applicationId, organizationId, environmentId, regionId with lowercase 'Id') are intentional to maintain consistency with respective event type conventions and config file property standards.

Applied to files:

  • platform-api/src/internal/model/gateway_event.go
📚 Learning: 2026-01-07T17:41:01.083Z
Learnt from: VirajSalaka
Repo: wso2/api-platform PR: 642
File: gateway/gateway-controller/pkg/utils/api_deployment.go:570-589
Timestamp: 2026-01-07T17:41:01.083Z
Learning: In gateway/gateway-controller/pkg/utils/api_deployment.go, when enableReplicaSync is true (multi-replica mode), the in-memory store (s.store) is intentionally not updated immediately during DeployAPIConfiguration. Instead, all replicas (including the originating replica) update their in-memory stores through the event-driven path via the EventListener to ensure consistent state synchronization across the cluster. This means immediate reads may not find the config until the EventListener processes the event, which is expected behavior.

Applied to files:

  • platform-api/src/internal/service/gateway_internal.go
📚 Learning: 2025-11-19T06:49:01.751Z
Learnt from: thivindu
Repo: wso2/api-platform PR: 162
File: platform-api/src/resources/openapi.yaml:299-343
Timestamp: 2025-11-19T06:49:01.751Z
Learning: In OpenAPI 3.1 specification, the `contentType` field within the `encoding` object (used for multipart/form-data) can be a comma-separated list of media types. This is valid per the OpenAPI 3.1 Encoding Object specification and should not be flagged as an error.

Applied to files:

  • platform-api/src/resources/openapi.yaml
🧬 Code graph analysis (7)
platform-api/src/internal/service/gateway_events.go (4)
platform-api/src/internal/model/gateway_event.go (1)
  • APIUndeploymentEvent (66-75)
platform-api/src/internal/dto/gateway_event.go (1)
  • GatewayEventDTO (21-33)
gateway/gateway-controller/pkg/api/generated/generated.go (1)
  • Payload (95-95)
platform-api/src/internal/websocket/stats.go (1)
  • DeliveryStats (31-51)
platform-api/src/internal/handler/gateway_internal.go (2)
platform-api/src/internal/utils/error.go (1)
  • NewErrorResponse (28-37)
platform-api/src/internal/constants/error.go (1)
  • ErrDeploymentNotActive (69-69)
platform-api/src/internal/handler/deployment.go (6)
platform-api/src/internal/service/deployment.go (1)
  • DeploymentService (37-45)
platform-api/src/internal/middleware/auth.go (1)
  • GetOrganizationFromContext (169-176)
platform-api/src/internal/utils/error.go (1)
  • NewErrorResponse (28-37)
platform-api/src/internal/dto/api.go (1)
  • DeployAPIRequest (166-170)
platform-api/src/internal/constants/error.go (3)
  • ErrAPINotFound (47-47)
  • ErrGatewayNotFound (62-62)
  • ErrDeploymentNotFound (68-68)
gateway/gateway-controller/pkg/api/generated/generated.go (3)
  • POST (227-227)
  • GET (225-225)
  • DELETE (224-224)
platform-api/src/internal/server/server.go (2)
platform-api/src/internal/service/deployment.go (1)
  • NewDeploymentService (48-66)
platform-api/src/internal/handler/deployment.go (1)
  • NewDeploymentHandler (38-42)
platform-api/src/internal/service/gateway_internal.go (1)
platform-api/src/internal/constants/error.go (1)
  • ErrDeploymentNotActive (69-69)
platform-api/src/internal/dto/api.go (1)
platform-api/src/internal/dto/common.go (1)
  • Pagination (23-27)
platform-api/src/internal/repository/api.go (1)
platform-api/src/internal/model/api.go (2)
  • APIDeployment (243-253)
  • APIDeployment (256-258)
🔇 Additional comments (23)
platform-api/src/config/config.go (2)

48-49: LGTM!

The new Deployments field follows the established pattern for nested configuration structs in this file, with proper envconfig tagging.


111-114: LGTM!

The Deployments struct is well-defined with a sensible default value. The envconfig tag follows the established naming convention.

platform-api/src/internal/model/gateway_event.go (1)

48-62: LGTM!

The APIDeploymentEvent struct changes align well with the PR's transition from revision-based to deployment-based architecture. The DeploymentID field with its deploymentId JSON tag maintains consistency with the existing naming conventions in this file.

platform-api/src/internal/constants/error.go (1)

67-70: LGTM!

The new deployment error constants follow the established naming convention and provide clear, descriptive error messages. Grouping them in a separate var block maintains consistency with other domain-specific error groups in this file.

platform-api/src/internal/database/schema.sql (1)

222-234: Verify intent: UNIQUE constraint may be redundant with PRIMARY KEY.

Since deployment_id is already the PRIMARY KEY (globally unique), the UNIQUE(api_uuid, gateway_uuid, deployment_id, organization_uuid) constraint provides no additional uniqueness enforcement—any combination including deployment_id will already be unique.

If the intent is to create a composite index for query performance, consider using an explicit index instead. If the intent is to enforce that each (api_uuid, gateway_uuid) pair can only have one deployment, the constraint should exclude deployment_id:

UNIQUE(api_uuid, gateway_uuid, organization_uuid)
platform-api/src/internal/handler/gateway_internal.go (1)

124-138: Active‑deployment lookup path looks good.
The gateway-scoped lookup and explicit 404 for “no active deployment” are clean and consistent.

platform-api/src/internal/server/server.go (1)

104-146: Deployment wiring is clean.
Service construction and handler registration align with the new deployment API surface.

platform-api/src/internal/model/api.go (1)

242-252: > Likely an incorrect or invalid review comment.

platform-api/src/resources/openapi.yaml (6)

792-826: Deploy endpoint definition looks consistent.
Request/response structure aligns with the new deployment flow.


828-855: Deployment list endpoint looks good.


856-908: Deployment get/delete endpoints look consistent.


3675-3768: Deployment schema additions look fine.


3971-3999: New deployment parameters are clear.


4009-4010: Tag update looks good.

platform-api/src/internal/repository/interfaces.go (1)

57-67: APIRepository deployment interface additions look good.

platform-api/src/internal/repository/api.go (2)

64-75: Expanded API queries and deployment count helper look consistent.

Also applies to: 124-135, 180-200, 229-266, 366-374, 1408-1422


996-1068: Deployment retrieval helpers look good.

Also applies to: 1070-1106, 1171-1208, 1210-1247

platform-api/src/internal/service/deployment.go (6)

36-65: DeploymentService wiring looks good.


68-95: Core deploy flow (validation/content generation/event broadcast) looks good.

Also applies to: 114-153, 155-199, 206-229


232-238: Redeploy/undeploy/delete/read flows look consistent.

Also applies to: 242-251, 259-295, 297-303, 307-335, 338-344, 348-358, 402-417, 421-432, 434-440, 444-451


360-399: Deployment listing DTO construction looks good.


454-479: Association helper logic looks fine.


482-573: Handle-based wrappers look consistent.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements a new deployment feature that replaces the previous revision-based deployment system with an immutable deployment artifact architecture. The change enables better deployment lifecycle management with versioned, immutable snapshots of API configurations.

Changes:

  • Introduces immutable deployment artifacts with lifecycle states (DEPLOYED/UNDEPLOYED)
  • Removes API revision fields and endpoints in favor of deployment-centric operations
  • Adds comprehensive deployment management endpoints (deploy, redeploy, undeploy, delete, retrieve content)

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
platform-api/src/resources/openapi.yaml Replaces revision-based deployment endpoints with new artifact-based deployment API; adds deployment schemas and removes revision schemas
platform-api/src/internal/service/deployment.go New service implementing complete deployment lifecycle with artifact creation, status management, and gateway event broadcasting
platform-api/src/internal/handler/deployment.go New handler exposing deployment REST endpoints with validation and error handling
platform-api/src/internal/repository/api.go Updates deployment repository methods to support artifact storage, filtering, and status management
platform-api/src/internal/repository/interfaces.go Extends APIRepository interface with comprehensive deployment artifact methods
platform-api/src/internal/service/api.go Removes revision deployment logic and related request structures
platform-api/src/internal/service/gateway_internal.go Adds active deployment retrieval by gateway for runtime consumption
platform-api/src/internal/service/gateway_events.go Adds undeployment event broadcasting capability
platform-api/src/internal/handler/api.go Removes revision deployment endpoint handler
platform-api/src/internal/handler/gateway_internal.go Updates GetAPI to fetch active deployment instead of current API state
platform-api/src/internal/utils/api.go Removes revision-related fields from DTO/Model conversions
platform-api/src/internal/model/api.go Updates API and APIDeployment models to remove revision fields and add artifact fields
platform-api/src/internal/model/gateway_event.go Changes deployment event to reference deploymentId instead of revisionId
platform-api/src/internal/dto/api.go Replaces APIRevisionDeployment with new deployment request/response DTOs
platform-api/src/internal/database/schema.sql Removes revision columns from apis table; transforms api_deployments into artifact storage table
platform-api/src/internal/constants/error.go Adds deployment-specific error constants
platform-api/src/internal/server/server.go Registers new deployment service and handler
platform-api/src/config/config.go Adds deployment configuration for artifact limits per API-gateway pair

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/service/deployment.go`:
- Around line 171-182: Only broadcast an undeployment event if there was an
active deployment for this API on that gateway: before constructing
APIUndeploymentEvent and calling
s.gatewayEventsService.BroadcastUndeploymentEvent(req.GatewayID, ...), query the
store for an active deployment for apiUUID and req.GatewayID (e.g. via an
existing method like s.deploymentStore.GetActiveDeployment or
s.store.GetActiveDeployment / HasActiveDeployment) and only proceed to create
the APIUndeploymentEvent and call BroadcastUndeploymentEvent when that query
confirms an active deployment exists.
♻️ Duplicate comments (9)
platform-api/src/internal/service/deployment.go (9)

96-113: Reorder limit enforcement and fail fast on undeploy errors.
Limit checks happen before undeploying the current deployment and delete at most one artifact; plus UpdateDeploymentStatus failures are only logged. This can exceed MaxPerAPIGateway or leave multiple active deployments.

🔧 Suggested tightening
-	if apiDeploymentCount >= s.cfg.Deployments.MaxPerAPIGateway {
-		// Delete oldest deployment in UNDEPLOYED state to make room
-		oldestDeployment, err := s.apiRepo.GetOldestUndeployedDeploymentByGateway(apiUUID, req.GatewayID, orgUUID)
-		if err != nil {
-			return nil, fmt.Errorf("failed to get oldest undeployed deployment: %w", err)
-		}
-		if oldestDeployment != nil {
-			if err := s.apiRepo.DeleteDeployment(oldestDeployment.DeploymentID, apiUUID, orgUUID); err != nil {
-				return nil, fmt.Errorf("failed to delete oldest undeployed deployment: %w", err)
-			}
-			log.Printf("[INFO] Deleted oldest undeployed deployment %s to make room for new deployment", oldestDeployment.DeploymentID)
-		}
-	}
+	for apiDeploymentCount >= s.cfg.Deployments.MaxPerAPIGateway {
+		oldestDeployment, err := s.apiRepo.GetOldestUndeployedDeploymentByGateway(apiUUID, req.GatewayID, orgUUID)
+		if err != nil {
+			return nil, fmt.Errorf("failed to get oldest undeployed deployment: %w", err)
+		}
+		if oldestDeployment == nil {
+			return nil, fmt.Errorf("deployment limit reached for api %s on gateway %s", apiUUID, req.GatewayID)
+		}
+		if err := s.apiRepo.DeleteDeployment(oldestDeployment.DeploymentID, apiUUID, orgUUID); err != nil {
+			return nil, fmt.Errorf("failed to delete oldest undeployed deployment: %w", err)
+		}
+		apiDeploymentCount--
+	}
@@
-	if existingDeployment != nil {
-		if err := s.apiRepo.UpdateDeploymentStatus(existingDeployment.DeploymentID, apiUUID, "UNDEPLOYED", orgUUID); err != nil {
-			log.Printf("[WARN] Failed to undeploy existing deployment %s: %v", existingDeployment.DeploymentID, err)
-		}
-	}
+	if existingDeployment != nil {
+		if err := s.apiRepo.UpdateDeploymentStatus(existingDeployment.DeploymentID, apiUUID, "UNDEPLOYED", orgUUID); err != nil {
+			return nil, fmt.Errorf("failed to undeploy existing deployment %s: %w", existingDeployment.DeploymentID, err)
+		}
+	}

Also applies to: 164-168


142-148: Use ErrDeploymentNotFound for missing base deployments.
This keeps handler error mapping consistent.

✅ Suggested fix
-		if baseDeployment == nil {
-			return nil, errors.New("base deployment not found")
-		}
+		if baseDeployment == nil {
+			return nil, constants.ErrDeploymentNotFound
+		}

201-204: Treat API–gateway association failure as fatal.
Logging and continuing can leave a deployed artifact without an association.

✅ Suggested fix
-	if err := s.ensureAPIGatewayAssociation(apiUUID, req.GatewayID, orgUUID); err != nil {
-		log.Printf("[WARN] Failed to ensure API-gateway association: %v", err)
-	}
+	if err := s.ensureAPIGatewayAssociation(apiUUID, req.GatewayID, orgUUID); err != nil {
+		return nil, fmt.Errorf("failed to ensure API-gateway association: %w", err)
+	}
Based on learnings, deployments are expected to auto-create API–gateway associations.

239-241: Return ErrDeploymentNotFound for missing deployments.

✅ Suggested fix
-	if deployment == nil {
-		return nil, errors.New("deployment not found")
-	}
+	if deployment == nil {
+		return nil, constants.ErrDeploymentNotFound
+	}

252-256: Undeploy failures should abort redeploy.
Ignoring this can leave multiple deployments marked active.

✅ Suggested fix
-	if existingDeployment != nil {
-		if err := s.apiRepo.UpdateDeploymentStatus(existingDeployment.DeploymentID, apiUUID, "UNDEPLOYED", orgUUID); err != nil {
-			log.Printf("[WARN] Failed to undeploy existing deployment %s: %v", existingDeployment.DeploymentID, err)
-		}
-	}
+	if existingDeployment != nil {
+		if err := s.apiRepo.UpdateDeploymentStatus(existingDeployment.DeploymentID, apiUUID, "UNDEPLOYED", orgUUID); err != nil {
+			return nil, fmt.Errorf("failed to undeploy existing deployment %s: %w", existingDeployment.DeploymentID, err)
+		}
+	}

304-309: Use ErrDeploymentNotFound / ErrDeploymentNotActive for consistent status mapping.
This aligns with handlers expecting sentinel errors.

✅ Suggested fix
-	if deployment == nil {
-		return errors.New("deployment not found")
-	}
-	if deployment.Status != "DEPLOYED" {
-		return errors.New("deployment is not currently active")
-	}
+	if deployment == nil {
+		return constants.ErrDeploymentNotFound
+	}
+	if deployment.Status != "DEPLOYED" {
+		return constants.ErrDeploymentNotActive
+	}

418-420: Use ErrDeploymentNotFound in GetDeployment.

✅ Suggested fix
-	if deployment == nil {
-		return nil, errors.New("deployment not found")
-	}
+	if deployment == nil {
+		return nil, constants.ErrDeploymentNotFound
+	}

441-443: Use ErrDeploymentNotFound in GetDeploymentContent.

✅ Suggested fix
-	if deployment == nil {
-		return nil, errors.New("deployment not found")
-	}
+	if deployment == nil {
+		return nil, constants.ErrDeploymentNotFound
+	}

575-584: Fix argument order in GetDeploymentContentByHandle.
The call currently swaps apiUUID and deploymentID, which will query the wrong content.

✅ Suggested fix
-	return s.GetDeploymentContent(deploymentID, apiUUID, orgUUID)
+	return s.GetDeploymentContent(apiUUID, deploymentID, orgUUID)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/service/deployment.go`:
- Around line 265-270: The code is discarding the error returned from
s.gatewayRepo.GetByUUID(deployment.GatewayID); update the block in the function
handling deployment events so you capture the returned error (e.g., gateway, err
:= s.gatewayRepo.GetByUUID(...)) and handle it instead of ignoring it: if err !=
nil, log or return the error (or emit a gatewayEventsService error event) and
avoid proceeding with an empty vhost; only set vhost = gateway.Vhost when
gateway != nil and err == nil. Ensure you reference s.gatewayEventsService,
s.gatewayRepo.GetByUUID, and deployment.GatewayID when making the change.
- Around line 146-148: Replace the string error returned when baseDeployment is
nil with the shared constant to ensure consistent error handling: change the
nil-check return in the baseDeployment branch (the "baseDeployment == nil" case)
to return constants.ErrDeploymentNotFound (ensure the constants package is
imported if not already) so handlers map this to the correct 404 behavior
instead of a generic 500.
- Around line 317-322: The code currently discards the error from
s.gatewayRepo.GetByUUID which can hide failures and produce events with an empty
vhost; change the call to capture and handle the error (e.g., gateway, err :=
s.gatewayRepo.GetByUUID(deployment.GatewayID)), and if err != nil either return
the error from the current function or log it with context (including
deployment.GatewayID) before continuing, ensuring you only use gateway.Vhost
when err == nil and gateway != nil; mirror the handling used in
RedeployDeployment for consistency and ensure s.gatewayEventsService receives a
valid vhost or a clear error is propagated.
♻️ Duplicate comments (6)
platform-api/src/internal/service/deployment.go (6)

165-169: Treat undeploy failures as fatal to maintain deployment consistency.

Ignoring the undeploy failure can leave multiple active deployments on the same gateway, violating the invariant of one active deployment per API per gateway.

💡 Suggested fix
 	if existingDeployment != nil {
 		if err := s.apiRepo.UpdateDeploymentStatus(existingDeployment.DeploymentID, apiUUID, "UNDEPLOYED", orgUUID); err != nil {
-			log.Printf("[WARN] Failed to undeploy existing deployment %s: %v", existingDeployment.DeploymentID, err)
+			return nil, fmt.Errorf("failed to undeploy existing deployment %s: %w", existingDeployment.DeploymentID, err)
 		}
 	}

171-182: Guard undeployment event broadcast with existingDeployment != nil check.

Currently, an undeployment event is broadcast even when there's no active deployment to undeploy, causing spurious events to be sent to gateways.

💡 Suggested fix
-	if s.gatewayEventsService != nil {
+	if existingDeployment != nil && s.gatewayEventsService != nil {

201-204: Treat API-gateway association failures as fatal or implement rollback.

If the association creation fails after the deployment is persisted, the system ends up with a deployed artifact without a proper gateway association. Based on learnings, deployments are expected to auto-create associations, so failure here should abort the operation.

💡 Suggested fix
 	// Ensure API-Gateway association exists
 	if err := s.ensureAPIGatewayAssociation(apiUUID, req.GatewayID, orgUUID); err != nil {
-		log.Printf("[WARN] Failed to ensure API-gateway association: %v", err)
+		// Rollback: delete the deployment we just created
+		if deleteErr := s.apiRepo.DeleteDeployment(deploymentID, apiUUID, orgUUID); deleteErr != nil {
+			log.Printf("[ERROR] Failed to rollback deployment %s after association failure: %v", deploymentID, deleteErr)
+		}
+		return nil, fmt.Errorf("failed to ensure API-gateway association: %w", err)
 	}

253-256: Treat undeploy failures as fatal in RedeployDeployment.

Same issue as in DeployAPI - ignoring this failure can leave multiple active deployments.


239-244: Use sentinel errors for consistent HTTP status mapping.

Both error cases use string errors. The "deployment not found" case should use constants.ErrDeploymentNotFound for 404 mapping. Consider defining constants.ErrDeploymentAlreadyActive for the second case.

💡 Suggested fix
 	if deployment == nil {
-		return nil, errors.New("deployment not found")
+		return nil, constants.ErrDeploymentNotFound
 	}
 	if deployment.Status == "DEPLOYED" {
-		return nil, errors.New("deployment is already active")
+		return nil, constants.ErrDeploymentAlreadyActive
 	}

304-309: Use sentinel errors for consistent HTTP status mapping.

Both error cases use string errors. Use constants.ErrDeploymentNotFound for the nil check and consider defining constants.ErrDeploymentNotActive for the status check.

💡 Suggested fix
 	if deployment == nil {
-		return errors.New("deployment not found")
+		return constants.ErrDeploymentNotFound
 	}
 	if deployment.Status != "DEPLOYED" {
-		return errors.New("deployment is not currently active")
+		return constants.ErrDeploymentNotActive
 	}
🧹 Nitpick comments (1)
platform-api/src/internal/service/deployment.go (1)

391-399: Consider adding pagination support for scalability.

The pagination values are hardcoded to return all results. For APIs with many deployments, this could become a performance concern. Consider accepting pagination parameters (offset, limit) and passing them to the repository.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/service/deployment.go`:
- Around line 242-244: Replace the ad-hoc errors.New calls used for deployment
state checks with the shared constants so handlers can map HTTP status codes via
errors.Is; specifically, in the branch that checks deployment.Status ==
"DEPLOYED" (the activation path) replace errors.New("deployment is already
active") with constants.ErrDeploymentIsDeployed, and in the branch that checks
for an inactive deployment (the deactivation/path around deployment.Status !=
"DEPLOYED") replace errors.New("deployment is not currently active") with
constants.ErrDeploymentNotActive; update the return statements in the functions
containing these checks to return those constant errors instead of new string
errors.
♻️ Duplicate comments (4)
platform-api/src/internal/service/deployment.go (4)

96-169: Enforce deployment limits after undeploy & fail fast on undeploy failures.

The limit check runs before undeploying and only deletes one undeployed artifact; this can exceed MaxPerAPIGateway. Also, UpdateDeploymentStatus(..., "UNDEPLOYED", ...) failures are logged and ignored, which can leave multiple active deployments.

Also applies to: 246-257


171-182: Only emit undeploy events when an active deployment existed.

Broadcasting an undeploy event without a prior active deployment can produce spurious gateway events.


201-204: Do not ignore API–gateway association failures.

Swallowing association errors can leave a deployed artifact without a gateway association. Treat this as fatal (or rollback) so deployment and association stay consistent. Based on learnings, deployments are expected to auto-create missing associations.


265-270: Handle gateway lookup errors for events.

The error from GetByUUID is discarded, which can emit events with an empty/incorrect vhost and mask underlying issues.

Also applies to: 317-322

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
platform-api/src/internal/repository/api.go (1)

1379-1418: Fix column scan order for deployment_id vs deployed_at.
The SELECT returns ad.deployment_id before ad.created_at, but the Scan assigns deployedAt first. This can cause scan type errors or swapped data.

💡 Suggested fix
 		err := rows.Scan(
 			&gateway.ID,
 			&gateway.OrganizationID,
 			&gateway.Name,
 			&gateway.DisplayName,
 			&gateway.Description,
 			&gateway.Vhost,
 			&gateway.IsCritical,
 			&gateway.FunctionalityType,
 			&gateway.IsActive,
 			&gateway.CreatedAt,
 			&gateway.UpdatedAt,
 			&gateway.AssociatedAt,
 			&gateway.AssociationUpdatedAt,
 			&gateway.IsDeployed,
-			&deployedAt,
-			&gateway.DeploymentId,
+			&gateway.DeploymentId,
+			&deployedAt,
 		)
♻️ Duplicate comments (2)
platform-api/src/internal/repository/api.go (2)

982-1013: Default deployment.Status to avoid CHECK violations.
If callers omit Status, this INSERT bypasses the DB default and can fail the CHECK constraint. Consider guarding with a sensible default before the insert (e.g., DEPLOYED).

💡 Suggested fix
 	// Generate UUID for deployment if not already set
 	if deployment.DeploymentID == "" {
 		deployment.DeploymentID = uuid.New().String()
 	}
+	if deployment.Status == "" {
+		deployment.Status = model.DeploymentStatusDeployed
+	}
 	deployment.CreatedAt = time.Now()

1211-1216: Make active deployment selection deterministic.
Multiple DEPLOYED rows can exist per (api_uuid, gateway_uuid). Without ordering, LIMIT 1 is arbitrary.

💡 Suggested fix
 	query := `
 		SELECT deployment_id, api_uuid, organization_uuid, gateway_uuid, status, base_deployment_id, content, metadata, created_at
 		FROM api_deployments
 		WHERE api_uuid = ? AND gateway_uuid = ? AND organization_uuid = ? AND status = ?
+		ORDER BY created_at DESC
 		LIMIT 1
 	`

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/handler/deployment.go`:
- Around line 304-318: Add a guard before calling
h.deploymentService.GetDeploymentsByHandle to validate the status query
parameter: when status is non-empty, check it against the canonical list of
allowed deployment statuses (use the existing constant/enum set if available or
a small local allowed set) and if it is not one of the allowed values return a
400 response via c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad
Request", "Invalid status query parameter")) and return; place this check
immediately after reading gatewayId and status and before invoking
GetDeploymentsByHandle (referencing variables status, gatewayId, apiId, orgId
and the call h.deploymentService.GetDeploymentsByHandle).
♻️ Duplicate comments (1)
platform-api/src/internal/handler/deployment.go (1)

239-239: Avoid sending a body with 204 responses.

c.JSON(http.StatusNoContent, nil) emits a null body; use c.Status(204) to keep the response truly empty.

🔧 Suggested fix
-	c.JSON(http.StatusNoContent, nil)
+	c.Status(http.StatusNoContent)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platform-api/src/resources/openapi.yaml`:
- Around line 843-855: The OpenAPI spec for the GetDeployments endpoint is
missing the 404 response that the GetDeployments handler returns when an API
handle doesn’t exist; update the endpoint's responses block in openapi.yaml to
add a '404' response (preferably referencing an existing
'#/components/responses/NotFound' or define one under components/responses if
missing) so the spec matches handler behavior and clients can rely on the
contract.
♻️ Duplicate comments (1)
platform-api/src/internal/handler/deployment.go (1)

309-313: Validate status query param before calling the service.

Spec already constrains status to DEPLOYED|UNDEPLOYED, but the handler forwards any value, which can yield a 500 for client mistakes. Add a small guard here to return 400 for invalid values.

🔧 Suggested guard
 	// Get optional query parameters
 	gatewayId := c.Query("gatewayId")
 	status := c.Query("status")
+	if status != "" && status != "DEPLOYED" && status != "UNDEPLOYED" {
+		c.JSON(http.StatusBadRequest, utils.NewErrorResponse(400, "Bad Request",
+			"Invalid status. Use DEPLOYED or UNDEPLOYED"))
+		return
+	}

 	deployments, err := h.deploymentService.GetDeploymentsByHandle(apiId, gatewayId, status, orgId)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/service/deployment.go`:
- Around line 68-219: In DeployAPI, when using an existing deployment base
(req.Base != "current"), guard against GetDeploymentByID returning a nil
baseDeployment before dereferencing baseDeployment.Content: after calling
s.apiRepo.GetDeploymentByID(req.Base, apiUUID, orgUUID) check if baseDeployment
== nil and return constants.ErrBaseDeploymentNotFound (or wrap as appropriate)
instead of proceeding to read baseDeployment.Content; keep existing error
handling for non-nil err and set baseDeploymentID = &req.Base and contentBytes =
baseDeployment.Content only after the nil check.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 17 comments.

@dushaniw dushaniw changed the title Deployment Feature [Platform API] Add management operations for Deployment Feature Jan 26, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/repository/api.go`:
- Around line 998-999: The code unconditionally dereferences
deployment.BaseDeploymentID into baseDeploymentID which will panic if
BaseDeploymentID is nil; update the handling in api.go (the block setting
baseDeploymentID) to check if deployment.BaseDeploymentID != nil and only assign
*deployment.BaseDeploymentID in that case, otherwise leave baseDeploymentID as
nil (or assign a nil interface{}), so downstream code receives a safe nil
instead of causing a nil pointer dereference.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@platform-api/src/internal/database/schema.sqlite.sql`:
- Around line 349-351: Add a partial UNIQUE index on api_deployments to ensure
only one active deployment exists per (api_uuid, gateway_uuid); specifically
create a UNIQUE index (e.g., idx_api_deployments_unique_active) on columns
api_uuid, gateway_uuid with a WHERE clause limiting rows to status = 'DEPLOYED'
so concurrent deploy/redeploy cannot produce multiple DEPLOYED rows. Add this
SQL alongside the other index statements for api_deployments (keeping the
existing non-unique indexes if still needed) and ensure the index name and WHERE
predicate reference the api_deployments table and the status value 'DEPLOYED'.

In `@platform-api/src/internal/repository/api.go`:
- Around line 1161-1172: UpdateDeploymentStatus currently accepts any status
string which can cause cryptic DB errors; add application-layer validation of
the status parameter against the same allowed enum values used by
GetDeploymentsByAPIUUID before executing the UPDATE. In UpdateDeploymentStatus
validate the incoming status (e.g., compare to the set/list of allowed
deployment statuses used by GetDeploymentsByAPIUUID or centralize into a shared
helper/constant), and if invalid return a clear error (e.g., "invalid deployment
status: <status>") instead of attempting the DB Exec; keep the rest of the
function logic unchanged.
🧹 Nitpick comments (1)
platform-api/src/internal/repository/api.go (1)

1030-1065: Consider extracting duplicated SELECT columns.

The SELECT column list is repeated 4 times across the query branches. This creates maintenance overhead when columns change.

♻️ Suggested refactor
+const deploymentSelectColumns = `deployment_id, api_uuid, organization_uuid, gateway_uuid, status, base_deployment_id, content, metadata, created_at`
+
 func (r *APIRepo) GetDeploymentsByAPIUUID(apiUUID, orgUUID string, gatewayID, status *string) ([]*model.APIDeployment, error) {
 	// ... validation ...
 
-	var query string
+	baseQuery := fmt.Sprintf(`SELECT %s FROM api_deployments WHERE api_uuid = ? AND organization_uuid = ?`, deploymentSelectColumns)
 	var args []interface{}
+	args = append(args, apiUUID, orgUUID)
 
-	if gatewayID != nil && status != nil {
-		query = `
-			SELECT deployment_id, api_uuid, organization_uuid, gateway_uuid, status, base_deployment_id, content, metadata, created_at
-			FROM api_deployments
-			WHERE api_uuid = ? AND organization_uuid = ? AND gateway_uuid = ? AND status = ?
-			ORDER BY created_at DESC
-		`
-		args = []interface{}{apiUUID, orgUUID, *gatewayID, *status}
-	} else if gatewayID != nil {
-		// ... similar duplication ...
+	if gatewayID != nil {
+		baseQuery += ` AND gateway_uuid = ?`
+		args = append(args, *gatewayID)
+	}
+	if status != nil {
+		baseQuery += ` AND status = ?`
+		args = append(args, *status)
 	}
+	baseQuery += ` ORDER BY created_at DESC`

@dushaniw dushaniw merged commit 605f968 into wso2:main Jan 27, 2026
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 29, 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