feat: GCS multi-image support for products - #10
Merged
Conversation
Replaces the single image_url field with a product_images table supporting multiple ordered images per product. Position 0 is the primary display image; image_url in ProductResponse is computed from it for backward compatibility. New endpoints (admin only, enforced by gateway): POST /api/products/:id/images — upload file to GCS, create DB record DELETE /api/products/:id/images/:id — remove from GCS and DB PUT /api/products/:id/images/reorder — reorder by position (0 = primary) StorageService interface with NoopStorage fallback means the service starts cleanly when GCS_BUCKET_NAME is unset — upload returns 503, all other product endpoints are unaffected. DeleteProduct now cleans up all GCS objects before removing the DB row. ProductImage table is auto-migrated alongside existing tables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gateway was missing the three image management routes, causing 404s.
Added POST/DELETE/PUT for /:id/images and /:id/images/reorder under
the admin-protected products group.
Migration was trying to call db.AutoMigrate(&ProductImage{}) in isolation,
which caused GORM to traverse relationships and generate invalid SQL
("insufficient arguments"). Replaced with idempotent raw SQL in
applySearchIndex so product_images is always created safely regardless
of whether the products table already existed.
Also updated API_DOCS.md and API_CURL_TESTS.md with the new image
endpoints, updated product response shape (images array + computed
image_url), and removed image_url from the create/update request body.
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
image_urlfield with aproduct_imagestable supporting multiple ordered images per product0is the primary image;image_urlin all product responses is now computed fromimages[0].urlfor backward compatibilityStorageServiceinterface with aNoopStoragefallback — service starts cleanly without GCS credentials, upload returns503New endpoints (admin only)
POST/api/products/:id/imagesDELETE/api/products/:id/images/:image_idPUT/api/products/:id/images/reorderBreaking change
image_urlis removed fromPOST /api/productsandPUT /api/products/:idrequest bodies. Images are managed exclusively via the new image endpoints after the product is created.What changed
product-service— newproduct_imagestable (raw SQL migration, idempotent),StorageServiceinterface + GCS implementation, 3 new handlers, image cleanup on product deleteapi-gateway— 3 new image routes registered under admin-protected products groupdocker-compose.yml+.env.example—GCS_BUCKET_NAMEandGCS_CREDENTIALS_JSONdocumenteddocs/API_DOCS.md+docs/API_CURL_TESTS.md— updated product response shape and new image endpoint docsTest plan
images[]populated,image_urlequalsimages[0].urlimage_urlupdates to reflect new primary400400404404GCS_BUCKET_NAMEset (NoopStorage fallback)🤖 Generated with Claude Code