Skip to content

Implement cart, order, and AI services with product integration - #1

Merged
Dubjay18 merged 8 commits into
mainfrom
feat/revamp-askshop
Sep 4, 2026
Merged

Dubjay18 merged 8 commits into
mainfrom
feat/revamp-askshop

Conversation

@Dubjay18

@Dubjay18 Dubjay18 commented Sep 4, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR completes the implementation of the cart, order, and AI services, adds comprehensive HTTP handlers and domain logic, integrates inter-service communication via gRPC and HTTP clients, and introduces event publishing over RabbitMQ. It also adds a docker-compose development environment and updates the API gateway to proxy all service endpoints.

Key Changes

Cart Service

  • Implemented CartManager and CartItemBuilder with full validation logic (quantity limits, price validation, discount application)
  • Added PGCartRepository for Postgres-backed cart persistence with user/session ID lookups
  • Implemented CartServiceImpl with product availability checks via gRPC client
  • Added comprehensive REST handler (CartHandler) with routes for CRUD operations and item management
  • Added unit tests for cart logic, item building, and validation

Order Service

  • Implemented OrderServiceImpl coordinating with cart-service over HTTP to validate and snapshot carts
  • Added PGOrderRepository for order persistence
  • Implemented REST handler (OrderHandler) with order placement, listing, and retrieval
  • Integrated RabbitMQ event publishing for order lifecycle events (placed, confirmed, cancelled)
  • Added HTTP client (cartclient) for inter-service communication with cart-service

AI Service

  • Implemented AIService with conversational shopping assistant using Google Gemini API
  • Added function-calling support for product search grounded in real catalog data
  • Implemented single-shot product explanations and cart-abandonment nudge generation
  • Added HTTP clients for product-service (search/fetch) and cart-service (abandoned cart retrieval)
  • Added REST handler (AIHandler) with /chat, /products/:id/explain, and /cart-nudges endpoints
  • Graceful degradation when GEMINI_API_KEY is unset

Product Service

  • Implemented PGProductRepository with full CRUD, pagination, and keyword search (name/slug/SKU)
  • Added InMemoryProductRepository as fallback when Postgres is unavailable
  • Implemented REST handler (ProductHandler) with GET/POST/PUT/DELETE endpoints and pagination
  • Added product seeding with realistic catalog data (iPhone, MacBook, etc.) with prices and stock
  • Added unit tests for slug generation and pagination clamping

Infrastructure & Integration

  • Added docker-compose.yml for lightweight local development (Postgres, RabbitMQ, all services via go run)
  • Implemented RabbitMQ event publisher/consumer in shared/events with graceful degradation
  • Added health check endpoint package (shared/health) for container orchestration
  • Updated API Gateway to proxy all service endpoints (products, cart, orders, AI)
  • Added gRPC client in cart-service for product availability checks
  • Implemented inter-service HTTP clients with proper error handling and status code preservation

Testing & CI

  • Added comprehensive unit tests for cart logic, product domain, and utility functions
  • Added GitHub Actions CI workflow for build and test verification
  • Added test coverage for pagination, slug generation, and cart validation

Documentation & Configuration

  • Added ARCHITECTURE.md with service map and communication patterns
  • Updated README.md with architecture overview and links to detailed docs
  • Updated Kubernetes deployment manifests with correct port mappings
  • Updated Tiltfile for local development with gRPC port forwarding
  • Fixed environment variable naming consistency across services

Notable Implementation Details

  • Graceful Degradation: Services start successfully even when optional dependencies (RabbitMQ, Gemini API) are unavailable; features degrade to no-ops rather than failing startup
  • Inter-Service Communication: Cart service uses gRPC for product checks (performance), order service uses HTTP for cart operations (simplicity), AI service uses HTTP for both
  • Event-Driven: Order placement publishes events over RabbitMQ for downstream consumption
  • Clean Architecture: All services follow domain-driven design with clear separation of concerns (domain, service, repository, HTTP handler layers)
  • Error Handling: Consistent error mapping across services preserves HTTP status codes through the API gateway

- Untrack root-level `api-gateway`, which was a 27MB compiled ELF
  binary accidentally committed rather than the "legacy artefact"
  source the README implied. Gitignore it going forward.
- gofmt the tree so the (separately staged) CI gate doesn't fail on
  pre-existing formatting drift (product-service, tools/create_service.go).
- Fix a real go vet failure in shared/util.GetRandomAvatar: %d format
  verb was used with a uuid.UUID string argument.
- Update README's Roadmap & Known Gaps to reflect actual per-service
  maturity.
- Corrected the auth-service assessment in the AskShop revamp plan:
  it's an empty stub like order/ai-service, not a duplicate in-memory
  auth implementation as the file listing initially suggested.
Closes the biggest gap identified in the revamp plan: product, cart,
and order services now form a real, working checkout path instead of
scaffolding with hardcoded placeholders.

- product-service: implement the Postgres repository and REST handler
  (both were stubs), add price/stock to the domain model and gRPC
  contract (shared/proto/product.proto), extend the service/repository
  interfaces with Create/Update/Delete, and wire it all into main.go
  with a graceful in-memory fallback when Postgres is unreachable.
  Seed data now carries real prices and stock levels.
- cart-service: was previously unwired (empty main.go) despite solid
  domain/service logic. Add a Postgres CartRepository, rewrite the
  REST handler to match the actual CartService interface (the old one
  referenced a CartInterface nothing implemented), and replace the
  hardcoded $29.99 placeholder in AddItem with a live gRPC lookup
  against product-service for name/SKU/price/stock. Implement the
  ValidateCartForCheckout TODO (price-change warnings, stock checks).
- order-service: build it from scratch (previously `func main() {}`).
  Validates the cart via cart-service, snapshots it into an Order,
  marks the cart converted, and publishes order.event.placed.
- shared/events: thin RabbitMQ publisher/consumer over a topic
  exchange, keyed by the routing keys already declared in
  shared/contracts/amqp.go. Degrades to a logged no-op if
  RABBITMQ_URL is unset/unreachable rather than failing startup.
  order-service runs a demonstration consumer proving the round trip.
- api-gateway: proxy routes for /products, /cart, /orders, forwarding
  caller identity via X-User-ID until JWT verification is wired
  through the gateway.

Verified locally end-to-end against real Postgres and RabbitMQ
instances: register/browse/cart/checkout all the way through order
creation, cart conversion, and event publish+consume, both service-
to-service and routed entirely through the gateway.
Builds ai-service into a real service (previously an empty stub) using
the official Anthropic Go SDK, grounded in the live catalog rather
than letting the model invent products.

- ai-service: conversational shopping assistant (POST /api/v1/ai/chat)
  using a manual tool-use loop where Claude calls a search_products
  tool backed by product-service before answering; product
  explanations (POST /api/v1/ai/products/:id/explain), which also
  publish ai.cmd.explain_product; and a cart-abandonment nudge batch
  endpoint (POST /api/v1/ai/cart-nudges) that generates re-engagement
  messages from cart-service's abandoned carts. Degrades cleanly to
  503 on every AI route when ANTHROPIC_API_KEY is unset, rather than
  failing to start or crashing on first request. Model configurable
  via AI_MODEL (default claude-opus-5).
- product-service: wire up real keyword search (?q=) using the
  existing but previously unused domain.ScopeSearch, both in the
  Postgres repository and the in-memory fallback. This is what the
  chat assistant's search_products tool calls.
- cart-service: add an internal GET /api/v1/cart/admin/abandoned
  endpoint (not proxied through the gateway) so ai-service can read
  abandoned carts for the nudge feature.
- api-gateway: proxy /api/v1/ai/chat and /api/v1/ai/products/:id/explain.

Verified locally: product search returns real seeded data; all three
AI endpoints correctly return 503 with a clear message when no
Anthropic API key is configured (no key was available in this
environment to exercise the live model call, so that path is
unverified beyond compiling against the documented SDK patterns).
Replaces the Anthropic Go SDK with the official Google Gen AI Go SDK
(google.golang.org/genai). Same shape as before, different provider:

- internal/llm/client.go: wraps *genai.Client instead of the Anthropic
  client, reading GEMINI_API_KEY (falling back to GOOGLE_API_KEY) via
  the Gemini API backend. Default model is now gemini-3.6-flash
  (override with AI_MODEL, unchanged env var).
- internal/service/chat_service.go: the shopping assistant's tool-use
  loop is rewritten against Gemini's function-calling API
  (FunctionDeclaration/Schema, resp.FunctionCalls(), resp.Text(),
  genai.NewContentFromFunctionResponse) in place of Anthropic's
  ToolParam/ToolUseBlock. Same behavior: search_products grounds every
  answer in the real catalog, product explanations and cart nudges
  are single-shot generations.
- Same graceful-degradation contract as before: every AI route
  returns 503 with a clear message when no key is configured, instead
  of failing to start.
- README and startup log messages updated to reference
  GEMINI_API_KEY/GOOGLE_API_KEY instead of ANTHROPIC_API_KEY.

Verified: builds and vets clean; ai-service starts and correctly
returns 503 without a key (no Gemini key was available in this
environment to exercise a live model call).
@gitguardian

gitguardian Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


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

- Unit tests for the pure domain logic that had none: cart-service's
  CartItemBuilder/CartManager (item validation, totals, cart
  validation, merge/abandonment detection) and Cart/CartItem methods;
  product-service's slugify and BeforeCreate hooks, plus the
  clampPagination pagination helper; and shared/contracts.JoinPaths
  and shared/util.GetRandomAvatar. go test ./... no longer reports
  "no test files" across the board.
- shared/health: a small Register(router, name) helper wired into
  every service (api-gateway, user, product, cart, order, ai) so each
  exposes a consistent GET /healthz. It's a liveness check only (no
  DB/broker ping yet).
- docker-compose.yml: a lighter local-dev path than Tilt/Kubernetes —
  Postgres + RabbitMQ + every Go service via `go run` against the
  mounted source tree, no image build step. Validated with
  `docker compose config` (no daemon was available in this
  environment to run it end-to-end).
- ARCHITECTURE.md: service map, checkout request flow, RabbitMQ event
  flow, and a documented list of known architectural gaps (no gateway
  JWT verification yet, the cart unique-user-id-index vs. converted-cart
  interaction, no distributed tracing).
- README: rewrote the now-stale "Architecture at a Glance" section
  (still described product/cart/order/ai as scaffolding), added the
  docker-compose instructions and a Testing section, and documented a
  real, verified bug found while researching this phase: the
  container ports in the Tilt/K8s dev manifests disagree with each
  service's actual HTTP_ADDR default and with each other. Left
  unfixed since it needs a real cluster to verify against, which
  wasn't available here — flagged instead of guessed at.

Verified: go build/vet/test all clean; docker compose config parses
without error; manually curled /healthz on a running service.
Aligns every k8s deployment's containerPort/Service port, and every
Tiltfile port_forward, with each service's actual HTTP_ADDR default
in code:

- product-service: containerPort/Service port 8083 -> 8082 (matches
  its real default), and adds a grpc containerPort/Service port 9090
  that was previously missing entirely — without it, cart-service's
  in-cluster gRPC pricing lookups against product-service would have
  had nothing to connect to.
- cart-service: 8086 -> 8083.
- ai-service: 8087 -> 8086.
- order-service and user-service's k8s manifests were already correct
  (8085/8084); only their Tilt port_forwards were wrong (mismatched
  with each other, not just with the code) and are now fixed.
- api-gateway-deployment.yaml/app-config.yaml: the configmap key was
  GATEWAY_HTTP_ADDR, but services/api-gateway/main.go reads HTTP_ADDR
  — renamed so the configmap actually controls the gateway's port
  instead of silently no-opping.
- product-seeder-job.yaml: bumped golang:1.21 -> golang:1.24-bookworm
  to match go.mod's `go 1.24.2` requirement (the job would have
  failed to build the seeder otherwise).

Verified: every edited YAML file parses (python yaml.safe_load_all),
Tiltfile brackets balance, go build/vet/test still clean. Not
verified against a live cluster — none was available in this
environment; this is a static consistency fix aligning declared
config with code, not something exercised end-to-end. README and
ARCHITECTURE.md updated accordingly.
Live end-to-end QA against real Postgres, RabbitMQ, and a working
Gemini key surfaced a critical authorization bug plus a systemic
status-code bug, both confirmed and fixed:

P0 — cart item IDOR: RemoveItem/UpdateItemQuantity/SaveForLater took
a userID parameter but never used it — cart-service operated on bare
item IDs with no check that the item belonged to the caller's cart.
Live-verified: User B could delete or overwrite User A's cart item
quantity just by knowing the item's UUID, and the response even
echoed A's full cart (including identity) back to B. Fixed with
assertItemOwnedByUser, called before every item mutation; returns the
same ErrItemNotFound in both the "not yours" and "doesn't exist"
cases so the fix doesn't add an existence oracle.

P1 — anonymous cart writes collided on one shared identity: omitting
X-User-ID let cart-service create/reuse a cart with userId "", so
every caller who forgot the header shared the same cart. cart-service
write/checkout routes now require it (400 if absent), matching what
order-service already enforced.

P1 — api-gateway collapsed every downstream error to 500: cart-service
correctly returns 400/409/422 for bad input, insufficient stock, and
failed checkout validation, but every proxy handler I added in
Phase 1/2 (cart, orders, ai) hardcoded 500 in its err != nil branch.
rest.ServiceClient now returns a typed *ServiceError carrying the real
status code, exposed via rest.StatusAndMessage(err), and every proxy
handler forwards it.

P2 — GET /api/v1/products/:id returned 500 for a malformed UUID
instead of 400 (Update/Delete already validated; Get didn't). Fixed
to match.

P2 — ai-service's own error mapping only special-cased LLM
unavailability; everything else was 500 regardless of cause.
Extended its product client with the same typed-status-error pattern
so 404/400 from product-service now propagate correctly.

Verified via a full live QA pass (services/api-gateway/rest/client.go's
new StatusAndMessage helper, cart-service's ownership check, etc.) —
register/login, browse/search, cart, checkout, order placement, and
the AI chat/explain endpoints against a real Gemini key all
re-verified working after the fixes, with no regressions to the
legitimate-owner paths. go build/vet/test clean.
@Dubjay18
Dubjay18 force-pushed the feat/revamp-askshop branch from 01e6238 to 5cab0f8 Compare September 4, 2026 10:49
@Dubjay18
Dubjay18 merged commit a5f2ffb into main Sep 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant