Przejdź do głównej zawartości

Galeria produktu

Każdy produkt może mieć kuratorowaną galerię assetów obrazu i wideo z trzema etykietami, które pinują łańcuch rozwiązywania storefront:

  • Base Image — główny hero na PDP. Dokładnie jeden na produkt.
  • Small Image — wyróżniony w pasku miniaturek. Dokładnie jeden na produkt.
  • Thumbnail — obraz karty listingu. Dokładnie jeden na produkt.

Każdy element galerii może mieć od jednej do trzech z tych etykiet (ten sam asset może być np. jednocześnie Base Image i Thumbnail). Invariant „dokładnie jeden na produkt per etykieta” egzekwuje ograniczenie bazy UNIQUE (product_id, label), więc kolizje wychodzą jako typowany błąd konfliktu zamiast cichej korupcji danych.

Publiczne API​

Verb + PathOdbiorcaCel
GET /api/v1/admin/catalog/products/:id/galleryadminLista galerii produktu
POST /api/v1/admin/catalog/products/:id/galleryadminDołączenie istniejącego assetu image/video z opcjonalnymi etykietami
POST .../gallery?replace=trueadminAtomowa podmiana: usuń konfliktowe etykiety z innych elementów tego produktu, potem przypisz
PATCH /api/v1/admin/catalog/products/:id/gallery/:itemIdadminAktualizacja etykiet (ten sam przełącznik ?replace=true)
DELETE /api/v1/admin/catalog/products/:id/gallery/:itemIdadminUsunięcie z galerii (wiersz Asset przeżywa)
PUT /api/v1/admin/catalog/products/:id/gallery/orderadminZmiana kolejności według listy id

Atomowa podmiana etykiet​

Bez query ?replace=true przypisanie etykiety, która już istnieje na innym elemencie, zwraca 409 GALLERY_LABEL_ALREADY_TAKEN. Z nim serwis najpierw usuwa konfliktowe przypisania, potem wstawia nowe — wszystko w jednej transakcji, więc współbieżni admini nigdy nie widzą stanu w połowie zastosowanym.

Błędy​

CodeStatusKiedy
GALLERY_LABEL_ALREADY_TAKEN409Konflikt etykiety bez ?replace=true
GALLERY_LABEL_LIMIT_EXCEEDED400Więcej niż 3 etykiety na jednym elemencie
ASSET_KIND_NOT_SUPPORTED400Rodzaj assetu ∉ {image, video}
GALLERY_ITEM_NOT_FOUND404Brak :itemId
PRODUCT_NOT_FOUND404Brak :id

Integracja ze storefrontem​

productDetail.gallery[] niesie {id, position, labels[], asset{id, kind, url}}. Komponent <GallerySwitcher> renderuje Base Image jako główny <img> (lub pierwszy element, gdy Base Image nie jest ustawiony) i emituje pasek miniaturek z Small Image oznaczonym przez data-small-image="true", żeby motywy mogły go wyróżnić.

Rozwiązywanie miniatury karty listingu​

ProductSummary.primaryAssetUrl (używane przez kartę listingu) jest rozwiązywane po stronie serwera łańcuchem: Thumbnail → Base Image → pierwszy element galerii → legacy wiersz product_assets → null.

Rozwiązywanie obrazu OG​

productDetail.seo.openGraph.imageUrl preferuje Base Image z galerii nad miniaturą listingu, żeby udostępnienia społecznościowe dostały hero marketera.

Magazynowanie​

gallery_items (id, product_id FK CASCADE, asset_id FK RESTRICT, position, timestamps) + gallery_item_labels (composite PK na (gallery_item_id, label), UNIQUE (product_id, label), CHECK label IN ('base_image', 'small_image', 'thumbnail')).