feat: Implement product service with gateway auth fixes - #4
Merged
Conversation
…ng, and full-text search capabilities
…ltering, sorting, and pagination
…nd data retrieval
- Replace RSA/RS256 JWT validation in api-gateway with HS256 HMAC to
match tokens issued by user-service; token claims now read user UUID
from the standard "sub" field instead of the missing "user_id" field
- Add JWT_SECRET env var to api-gateway config, docker-compose, and
.env files; remove defunct JWT_PUBLIC_KEY / PEM file loading
- Wire RequireAuth and RequireRole("admin") into all protected routes:
logout, /users/*, /cart/*, /orders/*, payments GET, inventory (admin),
and product/category write endpoints (admin); public GETs unchanged
- Stripe webhook route kept unauthenticated (Stripe signs its own payload)
- Add FIXES_PLAN.md with implementation plan for remaining tasks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 3 — Service layer:
- Replace all stub methods in product_service.go with real implementations
matching the domain.ProductService interface (context.Context + uuid.UUID)
- Cache-aside pattern for GetProductByID and GetProducts (Redis → DB fallback)
- Write methods (Create/Update/Delete) invalidate list cache and evict detail
cache; Kafka events published async in goroutine so they never block HTTP
- CreateProduct and UpdateProduct verify the target category exists first
- CreateCategory guards slug uniqueness before inserting
- normalizeFilter sets page/limit defaults and validates sort param
- buildListCacheKey is nil-safe for CategoryID, MinPrice, MaxPrice pointers
Task 4 — Entry point:
- Add main.go, cmd/{config,dotenv,infrastructure,kafka,run,server}.go
- Wire dependency graph: DB → repo, Redis → cache, Kafka → publisher → service → handler
- infrastructure.go runs AutoMigrate for Category/Product/Inventory then applies
the tsvector trigger SQL from db/004_create_search_index.up.sql via db.Exec
- kafka.go creates one kafka.Writer per product topic; ensureTopics on startup
- Graceful shutdown closes DB, Redis, and Kafka writers on SIGINT/SIGTERM
- Add internal/events/kafka_publisher.go (mirrors user-service pattern)
- Add internal/handler/product_handler.go with productBody DTO using float64
for price (clean JSON API) converted to genproto Decimal on service call
- Add internal/route/product_route.go registering all product + category routes
- Add Dockerfile (multi-stage golang:1.25-alpine → alpine:3.18, port 8082)
- Add .env.example; pull in gin, gorm/driver/postgres, kafka-go dependencies
Task 5 — Fix GenerateCacheKey nil panic:
- Guard filter.CategoryID, MinPrice, MaxPrice pointer dereferences
- Fix InvalidateProductList scan pattern to "product:list:*" matching new key format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace genproto decimal.Decimal with float64 for Product.Price, ProductRequest.Price, and ProductResponse.Price — genproto's Decimal does not implement sql.Scanner/driver.Valuer so GORM could not read or write price values from PostgreSQL at runtime - Remove BeforeSave GORM hook and buildSearchVector helper from Product; the PostgreSQL trigger installed by applySearchIndex handles tsvector population correctly — the Go-side hook was redundant and wrote plain text into a tsvector column which risks type errors on some PG versions - Remove genproto decimal import from handler; simplify toDomain() to assign b.Price (float64) directly now that domain type matches - Add REDIS_URL and KAFKA_BROKERS to product-service in docker-compose.yml; without REDIS_URL the service panicked at startup before serving requests - Remove stale commented-out duplicate import in product_repository.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
JWT_SECRET, and fixed claim extraction (subfield for user UUID).RequireAuthandRequireRole("admin")were defined but never applied. All protected routes (users, cart, orders, payments GET, inventory, product/category writes) now enforce authentication; public GETs and the Stripe webhook remain open.tsvector, Redis cache-aside (5 min TTL), list cache invalidation on writes, Kafka event publishing (product.created/updated/deleted),float64price type compatible with GORM.GenerateCacheKeynil panic —CategoryID,MinPrice, andMaxPricepointer fields are now guarded before dereference.REDIS_URLandKAFKA_BROKERSto product-service (missing caused startup crash); addedJWT_SECRETto api-gateway.BeforeSaveGORM hook — Replaced by the PostgreSQLBEFORE INSERT/UPDATEtrigger installed at migration time.Commits
b4faafa9d98d84e05aae1c49eda03a0c50d78508fbTest plan
go build ./...passes inservices/api-gatewayandservices/product-servicePOST /api/auth/loginreturns JWT; token accepted byGET /api/users/meGET /api/productswithout token → 200POST /api/productswithout token → 401POST /api/productswith admin token → 201GET /api/products?q=<term>returns full-text search resultsGET /api/products?min_price=10does not panic (nil pointer fix)docker compose upstarts product-service without crashing (Redis + Kafka env vars present)POST /api/payments/webhook/stripewithout token → proxied (not 401)🤖 Generated with Claude Code