Skip to content

feat(customer-portal): add deployed products, attachments, activities, products - #1313

Merged
cloby99 merged 2 commits into
wso2-open-operations:dev-app-csm-portalfrom
Rashmika998:feature/customer-portal-backend-v2-batch4
Jul 31, 2026
Merged

cloby99 merged 2 commits into
wso2-open-operations:dev-app-csm-portalfrom
Rashmika998:feature/customer-portal-backend-v2-batch4

Conversation

@Rashmika998

@Rashmika998 Rashmika998 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds 10 more endpoints to apps/customer-portal/backend-v2 (26 total):

  • Deployed products: POST /deployed-products/search, POST /deployed-products, PATCH /deployed-products/{id} — create/update only succeed against entity-service's ServiceNow data source (Postgres always 400s, documented in code/README/openapi). PATCH enforces entity-service's mutually-exclusive rule (either cores/tps/description or active=false, never both) — verified in testing below.
  • Attachments: POST /attachments, POST /attachments/search, GET /attachments/{id}/content, DELETE /attachments/{id} — the content endpoint is this backend's first binary (non-JSON) response. Adds entity.Client.doBinary alongside the existing JSON do()/getJSON()/postJSON()/patchJSON()/deleteJSON(); the handler explicitly sets Content-Disposition: attachment (a fresh response header this backend constructs, not just relayed from entity-service) so browsers never render an attachment inline.
  • Case activity feed: POST /cases/{id}/activities/search — entity-service's CaseActivity is a discriminated union on type (comment/attachment/field_change); the Go struct mirrors its exact non-pointer omitempty pattern for the type-specific fields rather than "improving" it to pointers, to keep the JSON shape identical.
  • Products: POST /products/search, POST /products/{id}/versions/search — reuses the same superset-struct technique from the accounts endpoints (PR feat(customer-portal): add accounts/updates/scim modules and next 5 endpoints #1302) for entity-service's Postgres/ServiceNow data-source-dependent response shapes.

Also updates openapi.yaml (10 new paths, ~24 new schemas), README.md, and CLAUDE.md (new notes on the json.RawMessage three-state pattern for UpdateDeployedProductRequest.Description, and the binary-response convention).

Test plan

  • go build ./..., go vet ./..., gofmt -l . all clean
  • gosec -fmt=text ./... reports 0 issues
  • openapi.yaml validated as well-formed YAML with all 24 paths / new schemas present (verified no content was lost despite a large diff from mid-file insertion)
  • Manually verified all 10 endpoints: PATCH /deployed-products/{id} correctly rejects neither-nor and both-at-once field combos with 400, passes with exactly one; invalid UUID path params return 400; valid requests against an unreachable upstream map cleanly to 500 (no leakage)
  • Wire up against a real running entity-service instance (ServiceNow data source, for the deployed-product write routes)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Attachment management with create, search, download, and delete operations
    • Case activity feed search functionality with change tracking
    • Deployed product management capabilities including search, create, and update operations
    • Product and product version search functionality
  • Documentation

    • Expanded API documentation with new endpoints and comprehensive schema definitions

…, products

Adds 10 more endpoints to backend-v2 (26 total): deployed-product
search/create/update, attachment create/search/download/delete, case
activity feed search, and product/product-version search.

- POST /deployed-products/search, POST /deployed-products,
  PATCH /deployed-products/{id} — create/update only succeed against
  entity-service's ServiceNow data source (Postgres always 400s).
  PATCH enforces entity-service's mutually-exclusive rule: either
  cores/tps/description or active=false, never both.
- POST /attachments, POST /attachments/search,
  GET /attachments/{id}/content, DELETE /attachments/{id} — the content
  endpoint is the first binary (non-JSON) response in this backend; adds
  entity.Client.doBinary alongside the existing JSON do()/getJSON()/
  postJSON()/patchJSON()/deleteJSON(), and the handler sets
  Content-Disposition: attachment itself rather than relying on
  entity-service's own response headers, since Content-Type/Disposition
  is a fresh response this backend constructs.
- POST /cases/{id}/activities/search — the discriminated-union
  CaseActivity type (comment/attachment/field_change) is mirrored
  field-for-field from entity-service, including its exact non-pointer
  omitempty pattern for the type-specific fields.
- POST /products/search, POST /products/{id}/versions/search — reuses
  the accounts pattern for entity-service's Postgres/ServiceNow
  data-source-dependent response shapes: a superset Go struct with
  ambiguous fields typed as *string, normalized into one contract by the
  DTO mapper.

Also updates openapi.yaml (10 new paths, 20 new schemas), README.md, and
CLAUDE.md (new notes on the json.RawMessage three-state pattern and the
binary-response convention).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added App/Customer Portal Type/New Feature Represents a request or task for a new feature labels Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Rashmika998, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f073f480-f337-4ce9-84ab-7e0d0af46e7b

📥 Commits

Reviewing files that changed from the base of the PR and between f4b8ca9 and 255183c.

📒 Files selected for processing (4)
  • apps/customer-portal/backend-v2/internal/entity/cases.go
  • apps/customer-portal/backend-v2/internal/entity/deployed_products.go
  • apps/customer-portal/backend-v2/internal/entity/products.go
  • apps/customer-portal/backend-v2/internal/handler/deployed_products.go
📝 Walkthrough

Walkthrough

The backend adds deployed-product, attachment, case-activity, product, and product-version APIs. It introduces entity contracts, DTO mappings, clients, handlers, routes, OpenAPI schemas, documentation, and binary attachment download handling.

Changes

Customer portal resource APIs

Layer / File(s) Summary
Resource contracts and response mappings
apps/customer-portal/backend-v2/internal/entity/types.go, apps/customer-portal/backend-v2/internal/dto/*, apps/customer-portal/backend-v2/openapi.yaml
Adds wire types, DTOs, mapping functions, and schemas for the new resources.
Entity-service operations
apps/customer-portal/backend-v2/internal/entity/*
Adds search, create, update, delete, activity, and binary attachment client operations.
HTTP exposure and API documentation
apps/customer-portal/backend-v2/internal/handler/*, apps/customer-portal/backend-v2/cmd/server/main.go, apps/customer-portal/backend-v2/openapi.yaml, apps/customer-portal/backend-v2/README.md, apps/customer-portal/backend-v2/CLAUDE.md
Adds authenticated handlers, validation, route registration, endpoint definitions, usage examples, and response-shaping guidance.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AttachmentHandler
  participant EntityClient
  participant EntityService
  Client->>AttachmentHandler: GET /attachments/{id}/content
  AttachmentHandler->>EntityClient: GetAttachmentContent(id)
  EntityClient->>EntityService: Authenticated binary GET
  EntityService-->>EntityClient: Bytes and Content-Type
  EntityClient-->>AttachmentHandler: Bytes and Content-Type
  AttachmentHandler-->>Client: Binary response with attachment disposition
Loading

Possibly related PRs

Suggested labels: Area/Backend

Suggested reviewers: cloby99, shayanmalinda

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides a detailed summary and test plan but omits most required template sections, including purpose, goals, release note, documentation, security checks, and test environment. Complete the required template sections and document the missing test, security, documentation, release, migration, and environment details.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the PR's main change by naming the four added customer-portal feature areas.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
apps/customer-portal/backend-v2/openapi.yaml (1)

1230-1267: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Unbounded array in GET /updates/product-update-levels response.

Static analysis flags the productUpdateLevels items array as having no maxItems constraint. Unlike the new paginated search endpoints in this diff, this response is not paginated, so nothing bounds the number of items returned or documented as a contract limit.

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

In `@apps/customer-portal/backend-v2/openapi.yaml` around lines 1230 - 1267, Add a
maxItems constraint to the array schema in the 200 response of the
getUpdatesProductUpdateLevels operation to document and enforce the maximum
number of ProductUpdateLevel items that can be returned. Since this endpoint is
not paginated, the array schema requires an explicit upper bound to define the
API contract and prevent unbounded responses.

Source: Linters/SAST tools

apps/customer-portal/backend-v2/internal/entity/client.go (1)

194-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared logic between do and doBinary.

doBinary duplicates the header-injection block and the bounded-read/error-excerpt block from do. A future change to authentication headers or error formatting needs to be applied in both places, which invites drift.

♻️ Proposed extraction of shared helpers
func (c *Client) setAuthHeaders(ctx context.Context, req *http.Request) {
	if token := userIDTokenFromContext(ctx); token != "" {
		req.Header.Set("x-user-id-token", token)
	}
	if id := correlationIDFromContext(ctx); id != "" {
		req.Header.Set("X-CSM-Correlation-ID", id)
	}
}

func readBoundedResponse(resp *http.Response, maxBytes int64) ([]byte, error) {
	limited := io.LimitReader(resp.Body, maxBytes+1)
	respBody, err := io.ReadAll(limited)
	if err != nil {
		return nil, fmt.Errorf("entity: read response body: %w", err)
	}
	if int64(len(respBody)) > maxBytes {
		return nil, fmt.Errorf("entity: response body exceeds %d bytes", maxBytes)
	}
	return respBody, nil
}

func upstreamError(resp *http.Response, respBody []byte) error {
	const maxErrBody = 256
	excerpt := respBody
	if len(excerpt) > maxErrBody {
		excerpt = excerpt[:maxErrBody]
	}
	return &apierror.Error{StatusCode: resp.StatusCode, Body: string(excerpt)}
}

do and doBinary both call setAuthHeaders, readBoundedResponse, and upstreamError instead of repeating the logic inline.

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

In `@apps/customer-portal/backend-v2/internal/entity/client.go` around lines 194 -
239, Extract three helper methods to eliminate duplication between do and
doBinary: create setAuthHeaders to handle the x-user-id-token and
X-CSM-Correlation-ID header injection, create readBoundedResponse to encapsulate
the io.LimitReader, io.ReadAll, and size validation logic, and create
upstreamError to format the error response with the status code and truncated
body excerpt. Update both do and doBinary to call these helpers instead of
repeating the logic inline, ensuring both methods use the same authentication
and error-handling paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/customer-portal/backend-v2/internal/entity/types.go`:
- Around line 693-700: Remove the unused ID field from
UpdateDeployedProductRequest, since PatchDeployedProduct already passes the path
ID separately to UpdateDeployedProduct and JSON decoding cannot populate it.
Keep the request payload fields unchanged and follow the existing pattern used
by SearchCaseActivitiesRequest.CaseID and
SearchProductVersionsRequest.ProductID.
- Around line 833-839: The SearchCaseActivitiesRequest struct contains a CaseID
field with a misleading comment, but the field is never populated—the caseID is
instead handled as a separate parameter in the SearchCaseActivities handler and
entity.Client.SearchCaseActivities method. Remove the unused CaseID field from
the SearchCaseActivitiesRequest struct and its associated doc comment to align
the code with the actual behavior.
- Around line 882-888: The SearchProductVersionsRequest struct contains a
ProductID field with a comment claiming it is populated from the URL path
parameter, but since the SearchProductVersions handler and
entity.Client.SearchProductVersions both accept productID as a separate function
parameter, this field is never assigned and remains unused. Remove the ProductID
field from the SearchProductVersionsRequest struct to eliminate the misleading
documentation and unused field, preserving the Pagination and SearchQuery
fields.
- Around line 616-624: Update SearchProducts-related types in types.go to match
entity-service/openapi.yaml: model the oneOf response as the required
discriminated wrapper containing SearchProductsResponse or
SearchSNProductsResponse, or consistently expose the two oneOf shapes with their
discriminator. Ensure SearchProducts and its response types serialize to the
OpenAPI contract without changing unrelated deployed-product types.

In `@apps/customer-portal/backend-v2/internal/handler/deployed_products.go`:
- Around line 131-143: Update PatchDeployedProduct to use a restricted customer
update DTO rather than decoding directly into
entity.UpdateDeployedProductRequest, excluding deploymentId unless it is
explicitly supported. Reject requests containing deploymentId before the
detailFieldsSet/activeSet validation and entity-service call; if retained, add
its customer-appropriate validation and include it in the update contract.

---

Nitpick comments:
In `@apps/customer-portal/backend-v2/internal/entity/client.go`:
- Around line 194-239: Extract three helper methods to eliminate duplication
between do and doBinary: create setAuthHeaders to handle the x-user-id-token and
X-CSM-Correlation-ID header injection, create readBoundedResponse to encapsulate
the io.LimitReader, io.ReadAll, and size validation logic, and create
upstreamError to format the error response with the status code and truncated
body excerpt. Update both do and doBinary to call these helpers instead of
repeating the logic inline, ensuring both methods use the same authentication
and error-handling paths.

In `@apps/customer-portal/backend-v2/openapi.yaml`:
- Around line 1230-1267: Add a maxItems constraint to the array schema in the
200 response of the getUpdatesProductUpdateLevels operation to document and
enforce the maximum number of ProductUpdateLevel items that can be returned.
Since this endpoint is not paginated, the array schema requires an explicit
upper bound to define the API contract and prevent unbounded responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4edb8dda-6211-4b93-8708-2ffc1f675d23

📥 Commits

Reviewing files that changed from the base of the PR and between 4a1b5c9 and f4b8ca9.

📒 Files selected for processing (18)
  • apps/customer-portal/backend-v2/CLAUDE.md
  • apps/customer-portal/backend-v2/README.md
  • apps/customer-portal/backend-v2/cmd/server/main.go
  • apps/customer-portal/backend-v2/internal/dto/attachment.go
  • apps/customer-portal/backend-v2/internal/dto/case.go
  • apps/customer-portal/backend-v2/internal/dto/deployed_product.go
  • apps/customer-portal/backend-v2/internal/dto/product.go
  • apps/customer-portal/backend-v2/internal/entity/attachments.go
  • apps/customer-portal/backend-v2/internal/entity/cases.go
  • apps/customer-portal/backend-v2/internal/entity/client.go
  • apps/customer-portal/backend-v2/internal/entity/deployed_products.go
  • apps/customer-portal/backend-v2/internal/entity/products.go
  • apps/customer-portal/backend-v2/internal/entity/types.go
  • apps/customer-portal/backend-v2/internal/handler/attachments.go
  • apps/customer-portal/backend-v2/internal/handler/cases.go
  • apps/customer-portal/backend-v2/internal/handler/deployed_products.go
  • apps/customer-portal/backend-v2/internal/handler/products.go
  • apps/customer-portal/backend-v2/openapi.yaml

Comment thread apps/customer-portal/backend-v2/internal/entity/types.go
Comment thread apps/customer-portal/backend-v2/internal/entity/types.go
Comment thread apps/customer-portal/backend-v2/internal/entity/types.go
Comment thread apps/customer-portal/backend-v2/internal/entity/types.go
- entity.Client.UpdateDeployedProduct/SearchCaseActivities/SearchProductVersions
  now actually assign the path-derived ID/CaseID/ProductID fields (json:"-",
  so no wire-format change) instead of leaving them permanently zero-valued,
  fixing a mismatch between the struct doc comments (which claimed these
  fields were path-populated) and the actual code.
- Documented why PATCH /deployed-products/{id} decodes directly into
  entity.UpdateDeployedProductRequest without a restricted portal DTO:
  DeploymentID is an IDOR-style scope guard per entity-service's own doc
  comment, not an internal-only mutation field like the ones excluded from
  the case-update endpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cloby99
cloby99 merged commit d85bf42 into wso2-open-operations:dev-app-csm-portal Jul 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App/Customer Portal Type/New Feature Represents a request or task for a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants