feat: fruit overview with thumbnails and card layout (story #07)
- Auto-crop thumbnails on image upload (Go stdlib, no external deps): find non-background bounding box, pad 5px, scale to ≤300px, encode JPEG - Add thumbnail_data BYTEA column to fruit_images and publication_fruit_images - New GET /api/v1/fruits/:id/images/:imageId/thumbnail endpoint (fallback to full data) - New GET /api/v1/publications/:id/fruit-images/:imgId/thumbnail endpoint - New POST /api/v1/admin/backfill-thumbnails to retroactively generate thumbnails - ListFruits response now includes synonyms and thumbnail_url per fruit - Replace FruitList table with responsive FruitCard grid (thumbnail + name + OSDB ID + type + synonyms) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ type FruitRepository interface {
|
||||
ListImages(ctx context.Context, fruitID int) ([]domain.FruitImage, error)
|
||||
AddImage(ctx context.Context, fruitID int, filename *string, data []byte, imageType string, title *string) (domain.FruitImage, error)
|
||||
GetImageData(ctx context.Context, fruitID, imageID int) ([]byte, error)
|
||||
GetThumbnailData(ctx context.Context, fruitID, imageID int) ([]byte, error)
|
||||
DeleteImage(ctx context.Context, fruitID, imageID int) error
|
||||
}
|
||||
|
||||
@@ -291,3 +292,20 @@ func (h *FruitHandler) DeleteImage(c echo.Context) error {
|
||||
}
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *FruitHandler) ServeThumbnail(c echo.Context) error {
|
||||
fruitID, err := parseID(c, "id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imageID, err := parseID(c, "imageId")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := h.repo.GetThumbnailData(c.Request().Context(), fruitID, imageID)
|
||||
if err != nil {
|
||||
return mapRepoError(c, err)
|
||||
}
|
||||
contentType := http.DetectContentType(data)
|
||||
return c.Blob(http.StatusOK, contentType, data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user