feat(front-end): back-office desing improvements

This commit is contained in:
2026-03-10 10:53:30 +01:00
parent a212a1d8cc
commit e747a9820e
47 changed files with 4861 additions and 310 deletions

View File

@@ -12,8 +12,20 @@ public class AdminShopProductDto {
private String categorySlug;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String excerpt;
private String excerptIt;
private String excerptEn;
private String excerptDe;
private String excerptFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoDescription;
private String ogTitle;
@@ -83,6 +95,38 @@ public class AdminShopProductDto {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getExcerpt() {
return excerpt;
}
@@ -91,6 +135,38 @@ public class AdminShopProductDto {
this.excerpt = excerpt;
}
public String getExcerptIt() {
return excerptIt;
}
public void setExcerptIt(String excerptIt) {
this.excerptIt = excerptIt;
}
public String getExcerptEn() {
return excerptEn;
}
public void setExcerptEn(String excerptEn) {
this.excerptEn = excerptEn;
}
public String getExcerptDe() {
return excerptDe;
}
public void setExcerptDe(String excerptDe) {
this.excerptDe = excerptDe;
}
public String getExcerptFr() {
return excerptFr;
}
public void setExcerptFr(String excerptFr) {
this.excerptFr = excerptFr;
}
public String getDescription() {
return description;
}
@@ -99,6 +175,38 @@ public class AdminShopProductDto {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}

View File

@@ -7,8 +7,20 @@ public class AdminUpsertShopProductRequest {
private UUID categoryId;
private String slug;
private String name;
private String nameIt;
private String nameEn;
private String nameDe;
private String nameFr;
private String excerpt;
private String excerptIt;
private String excerptEn;
private String excerptDe;
private String excerptFr;
private String description;
private String descriptionIt;
private String descriptionEn;
private String descriptionDe;
private String descriptionFr;
private String seoTitle;
private String seoDescription;
private String ogTitle;
@@ -43,6 +55,38 @@ public class AdminUpsertShopProductRequest {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getExcerpt() {
return excerpt;
}
@@ -51,6 +95,38 @@ public class AdminUpsertShopProductRequest {
this.excerpt = excerpt;
}
public String getExcerptIt() {
return excerptIt;
}
public void setExcerptIt(String excerptIt) {
this.excerptIt = excerptIt;
}
public String getExcerptEn() {
return excerptEn;
}
public void setExcerptEn(String excerptEn) {
this.excerptEn = excerptEn;
}
public String getExcerptDe() {
return excerptDe;
}
public void setExcerptDe(String excerptDe) {
this.excerptDe = excerptDe;
}
public String getExcerptFr() {
return excerptFr;
}
public void setExcerptFr(String excerptFr) {
this.excerptFr = excerptFr;
}
public String getDescription() {
return description;
}
@@ -59,6 +135,38 @@ public class AdminUpsertShopProductRequest {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}

View File

@@ -15,6 +15,7 @@ import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
@Entity
@@ -23,6 +24,8 @@ import java.util.UUID;
@Index(name = "ix_shop_product_featured_sort", columnList = "is_featured, is_active, sort_order")
})
public class ShopProduct {
public static final List<String> SUPPORTED_LANGUAGES = List.of("it", "en", "de", "fr");
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "shop_product_id", nullable = false)
@@ -38,12 +41,48 @@ public class ShopProduct {
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
private String name;
@Column(name = "name_it", length = Integer.MAX_VALUE)
private String nameIt;
@Column(name = "name_en", length = Integer.MAX_VALUE)
private String nameEn;
@Column(name = "name_de", length = Integer.MAX_VALUE)
private String nameDe;
@Column(name = "name_fr", length = Integer.MAX_VALUE)
private String nameFr;
@Column(name = "excerpt", length = Integer.MAX_VALUE)
private String excerpt;
@Column(name = "excerpt_it", length = Integer.MAX_VALUE)
private String excerptIt;
@Column(name = "excerpt_en", length = Integer.MAX_VALUE)
private String excerptEn;
@Column(name = "excerpt_de", length = Integer.MAX_VALUE)
private String excerptDe;
@Column(name = "excerpt_fr", length = Integer.MAX_VALUE)
private String excerptFr;
@Column(name = "description", length = Integer.MAX_VALUE)
private String description;
@Column(name = "description_it", length = Integer.MAX_VALUE)
private String descriptionIt;
@Column(name = "description_en", length = Integer.MAX_VALUE)
private String descriptionEn;
@Column(name = "description_de", length = Integer.MAX_VALUE)
private String descriptionDe;
@Column(name = "description_fr", length = Integer.MAX_VALUE)
private String descriptionFr;
@Column(name = "seo_title", length = Integer.MAX_VALUE)
private String seoTitle;
@@ -152,6 +191,38 @@ public class ShopProduct {
this.name = name;
}
public String getNameIt() {
return nameIt;
}
public void setNameIt(String nameIt) {
this.nameIt = nameIt;
}
public String getNameEn() {
return nameEn;
}
public void setNameEn(String nameEn) {
this.nameEn = nameEn;
}
public String getNameDe() {
return nameDe;
}
public void setNameDe(String nameDe) {
this.nameDe = nameDe;
}
public String getNameFr() {
return nameFr;
}
public void setNameFr(String nameFr) {
this.nameFr = nameFr;
}
public String getExcerpt() {
return excerpt;
}
@@ -160,6 +231,38 @@ public class ShopProduct {
this.excerpt = excerpt;
}
public String getExcerptIt() {
return excerptIt;
}
public void setExcerptIt(String excerptIt) {
this.excerptIt = excerptIt;
}
public String getExcerptEn() {
return excerptEn;
}
public void setExcerptEn(String excerptEn) {
this.excerptEn = excerptEn;
}
public String getExcerptDe() {
return excerptDe;
}
public void setExcerptDe(String excerptDe) {
this.excerptDe = excerptDe;
}
public String getExcerptFr() {
return excerptFr;
}
public void setExcerptFr(String excerptFr) {
this.excerptFr = excerptFr;
}
public String getDescription() {
return description;
}
@@ -168,6 +271,38 @@ public class ShopProduct {
this.description = description;
}
public String getDescriptionIt() {
return descriptionIt;
}
public void setDescriptionIt(String descriptionIt) {
this.descriptionIt = descriptionIt;
}
public String getDescriptionEn() {
return descriptionEn;
}
public void setDescriptionEn(String descriptionEn) {
this.descriptionEn = descriptionEn;
}
public String getDescriptionDe() {
return descriptionDe;
}
public void setDescriptionDe(String descriptionDe) {
this.descriptionDe = descriptionDe;
}
public String getDescriptionFr() {
return descriptionFr;
}
public void setDescriptionFr(String descriptionFr) {
this.descriptionFr = descriptionFr;
}
public String getSeoTitle() {
return seoTitle;
}
@@ -247,4 +382,94 @@ public class ShopProduct {
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String getNameForLanguage(String language) {
return resolveLocalizedValue(language, name, nameIt, nameEn, nameDe, nameFr);
}
public void setNameForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> nameIt = value;
case "en" -> nameEn = value;
case "de" -> nameDe = value;
case "fr" -> nameFr = value;
default -> {
}
}
}
public String getExcerptForLanguage(String language) {
return resolveLocalizedValue(language, excerpt, excerptIt, excerptEn, excerptDe, excerptFr);
}
public void setExcerptForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> excerptIt = value;
case "en" -> excerptEn = value;
case "de" -> excerptDe = value;
case "fr" -> excerptFr = value;
default -> {
}
}
}
public String getDescriptionForLanguage(String language) {
return resolveLocalizedValue(language, description, descriptionIt, descriptionEn, descriptionDe, descriptionFr);
}
public void setDescriptionForLanguage(String language, String value) {
switch (normalizeLanguage(language)) {
case "it" -> descriptionIt = value;
case "en" -> descriptionEn = value;
case "de" -> descriptionDe = value;
case "fr" -> descriptionFr = value;
default -> {
}
}
}
private String resolveLocalizedValue(String language,
String fallback,
String valueIt,
String valueEn,
String valueDe,
String valueFr) {
String normalizedLanguage = normalizeLanguage(language);
String preferred = switch (normalizedLanguage) {
case "it" -> valueIt;
case "en" -> valueEn;
case "de" -> valueDe;
case "fr" -> valueFr;
default -> null;
};
String resolved = firstNonBlank(preferred, fallback);
if (resolved != null) {
return resolved;
}
return firstNonBlank(valueIt, valueEn, valueDe, valueFr);
}
private String normalizeLanguage(String language) {
if (language == null) {
return "";
}
String normalized = language.trim().toLowerCase();
int separatorIndex = normalized.indexOf('-');
if (separatorIndex > 0) {
normalized = normalized.substring(0, separatorIndex);
}
return normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
}

View File

@@ -22,6 +22,7 @@ import com.printcalculator.service.SlicerService;
import com.printcalculator.service.media.PublicMediaQueryService;
import com.printcalculator.service.shop.ShopStorageService;
import com.printcalculator.service.storage.ClamAVService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -75,6 +76,7 @@ public class AdminShopProductControllerService {
private final ShopStorageService shopStorageService;
private final SlicerService slicerService;
private final ClamAVService clamAVService;
private final long maxModelFileSizeBytes;
public AdminShopProductControllerService(ShopProductRepository shopProductRepository,
ShopCategoryRepository shopCategoryRepository,
@@ -86,7 +88,8 @@ public class AdminShopProductControllerService {
AdminMediaControllerService adminMediaControllerService,
ShopStorageService shopStorageService,
SlicerService slicerService,
ClamAVService clamAVService) {
ClamAVService clamAVService,
@Value("${shop.model.max-file-size-bytes:104857600}") long maxModelFileSizeBytes) {
this.shopProductRepository = shopProductRepository;
this.shopCategoryRepository = shopCategoryRepository;
this.shopProductVariantRepository = shopProductVariantRepository;
@@ -98,6 +101,7 @@ public class AdminShopProductControllerService {
this.shopStorageService = shopStorageService;
this.slicerService = slicerService;
this.clamAVService = clamAVService;
this.maxModelFileSizeBytes = maxModelFileSizeBytes;
}
public List<AdminShopProductDto> getProducts() {
@@ -113,13 +117,13 @@ public class AdminShopProductControllerService {
@Transactional
public AdminShopProductDto createProduct(AdminUpsertShopProductRequest payload) {
ensurePayload(payload);
String normalizedName = normalizeRequired(payload.getName(), "Product name is required");
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), normalizedName);
LocalizedProductContent localizedContent = normalizeLocalizedProductContent(payload);
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), localizedContent.defaultName());
ensureSlugAvailable(normalizedSlug, null);
ShopProduct product = new ShopProduct();
product.setCreatedAt(OffsetDateTime.now());
applyProductPayload(product, payload, normalizedName, normalizedSlug, resolveCategory(payload.getCategoryId()));
applyProductPayload(product, payload, localizedContent, normalizedSlug, resolveCategory(payload.getCategoryId()));
ShopProduct saved = shopProductRepository.save(product);
syncVariants(saved, payload.getVariants());
return getProduct(saved.getId());
@@ -131,11 +135,11 @@ public class AdminShopProductControllerService {
ShopProduct product = shopProductRepository.findById(productId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Shop product not found"));
String normalizedName = normalizeRequired(payload.getName(), "Product name is required");
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), normalizedName);
LocalizedProductContent localizedContent = normalizeLocalizedProductContent(payload);
String normalizedSlug = normalizeAndValidateSlug(payload.getSlug(), localizedContent.defaultName());
ensureSlugAvailable(normalizedSlug, productId);
applyProductPayload(product, payload, normalizedName, normalizedSlug, resolveCategory(payload.getCategoryId()));
applyProductPayload(product, payload, localizedContent, normalizedSlug, resolveCategory(payload.getCategoryId()));
ShopProduct saved = shopProductRepository.save(product);
syncVariants(saved, payload.getVariants());
return getProduct(saved.getId());
@@ -291,14 +295,26 @@ public class AdminShopProductControllerService {
private void applyProductPayload(ShopProduct product,
AdminUpsertShopProductRequest payload,
String normalizedName,
LocalizedProductContent localizedContent,
String normalizedSlug,
ShopCategory category) {
product.setCategory(category);
product.setSlug(normalizedSlug);
product.setName(normalizedName);
product.setExcerpt(normalizeOptional(payload.getExcerpt()));
product.setDescription(normalizeOptional(payload.getDescription()));
product.setName(localizedContent.defaultName());
product.setNameIt(localizedContent.names().get("it"));
product.setNameEn(localizedContent.names().get("en"));
product.setNameDe(localizedContent.names().get("de"));
product.setNameFr(localizedContent.names().get("fr"));
product.setExcerpt(localizedContent.defaultExcerpt());
product.setExcerptIt(localizedContent.excerpts().get("it"));
product.setExcerptEn(localizedContent.excerpts().get("en"));
product.setExcerptDe(localizedContent.excerpts().get("de"));
product.setExcerptFr(localizedContent.excerpts().get("fr"));
product.setDescription(localizedContent.defaultDescription());
product.setDescriptionIt(localizedContent.descriptions().get("it"));
product.setDescriptionEn(localizedContent.descriptions().get("en"));
product.setDescriptionDe(localizedContent.descriptions().get("de"));
product.setDescriptionFr(localizedContent.descriptions().get("fr"));
product.setSeoTitle(normalizeOptional(payload.getSeoTitle()));
product.setSeoDescription(normalizeOptional(payload.getSeoDescription()));
product.setOgTitle(normalizeOptional(payload.getOgTitle()));
@@ -436,8 +452,20 @@ public class AdminShopProductControllerService {
dto.setCategorySlug(product.getCategory() != null ? product.getCategory().getSlug() : null);
dto.setSlug(product.getSlug());
dto.setName(product.getName());
dto.setNameIt(product.getNameIt());
dto.setNameEn(product.getNameEn());
dto.setNameDe(product.getNameDe());
dto.setNameFr(product.getNameFr());
dto.setExcerpt(product.getExcerpt());
dto.setExcerptIt(product.getExcerptIt());
dto.setExcerptEn(product.getExcerptEn());
dto.setExcerptDe(product.getExcerptDe());
dto.setExcerptFr(product.getExcerptFr());
dto.setDescription(product.getDescription());
dto.setDescriptionIt(product.getDescriptionIt());
dto.setDescriptionEn(product.getDescriptionEn());
dto.setDescriptionDe(product.getDescriptionDe());
dto.setDescriptionFr(product.getDescriptionFr());
dto.setSeoTitle(product.getSeoTitle());
dto.setSeoDescription(product.getSeoDescription());
dto.setOgTitle(product.getOgTitle());
@@ -523,6 +551,61 @@ public class AdminShopProductControllerService {
}
}
private LocalizedProductContent normalizeLocalizedProductContent(AdminUpsertShopProductRequest payload) {
String legacyName = normalizeOptional(payload.getName());
String fallbackName = firstNonBlank(
legacyName,
normalizeOptional(payload.getNameIt()),
normalizeOptional(payload.getNameEn()),
normalizeOptional(payload.getNameDe()),
normalizeOptional(payload.getNameFr())
);
if (fallbackName == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Product name is required");
}
Map<String, String> names = new LinkedHashMap<>();
names.put("it", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameIt()), fallbackName), "Italian product name is required"));
names.put("en", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameEn()), fallbackName), "English product name is required"));
names.put("de", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameDe()), fallbackName), "German product name is required"));
names.put("fr", normalizeRequired(firstNonBlank(normalizeOptional(payload.getNameFr()), fallbackName), "French product name is required"));
String fallbackExcerpt = firstNonBlank(
normalizeOptional(payload.getExcerpt()),
normalizeOptional(payload.getExcerptIt()),
normalizeOptional(payload.getExcerptEn()),
normalizeOptional(payload.getExcerptDe()),
normalizeOptional(payload.getExcerptFr())
);
Map<String, String> excerpts = new LinkedHashMap<>();
excerpts.put("it", firstNonBlank(normalizeOptional(payload.getExcerptIt()), fallbackExcerpt));
excerpts.put("en", firstNonBlank(normalizeOptional(payload.getExcerptEn()), fallbackExcerpt));
excerpts.put("de", firstNonBlank(normalizeOptional(payload.getExcerptDe()), fallbackExcerpt));
excerpts.put("fr", firstNonBlank(normalizeOptional(payload.getExcerptFr()), fallbackExcerpt));
String fallbackDescription = firstNonBlank(
normalizeOptional(payload.getDescription()),
normalizeOptional(payload.getDescriptionIt()),
normalizeOptional(payload.getDescriptionEn()),
normalizeOptional(payload.getDescriptionDe()),
normalizeOptional(payload.getDescriptionFr())
);
Map<String, String> descriptions = new LinkedHashMap<>();
descriptions.put("it", firstNonBlank(normalizeOptional(payload.getDescriptionIt()), fallbackDescription));
descriptions.put("en", firstNonBlank(normalizeOptional(payload.getDescriptionEn()), fallbackDescription));
descriptions.put("de", firstNonBlank(normalizeOptional(payload.getDescriptionDe()), fallbackDescription));
descriptions.put("fr", firstNonBlank(normalizeOptional(payload.getDescriptionFr()), fallbackDescription));
return new LocalizedProductContent(
names.get("it"),
firstNonBlank(excerpts.get("it"), fallbackExcerpt),
firstNonBlank(descriptions.get("it"), fallbackDescription),
names,
excerpts,
descriptions
);
}
private void ensureSlugAvailable(String slug, UUID currentProductId) {
shopProductRepository.findBySlugIgnoreCase(slug).ifPresent(existing -> {
if (currentProductId == null || !existing.getId().equals(currentProductId)) {
@@ -547,6 +630,18 @@ public class AdminShopProductControllerService {
return normalized.isBlank() ? null : normalized;
}
private String firstNonBlank(String... values) {
if (values == null) {
return null;
}
for (String value : values) {
if (value != null && !value.isBlank()) {
return value;
}
}
return null;
}
private String normalizeAndValidateSlug(String slug, String fallbackName) {
String source = normalizeOptional(slug);
if (source == null) {
@@ -579,6 +674,9 @@ public class AdminShopProductControllerService {
if (file == null || file.isEmpty()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "3D model file is required");
}
if (maxModelFileSizeBytes > 0 && file.getSize() > maxModelFileSizeBytes) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "3D model file exceeds size limit");
}
String extension = resolveExtension(sanitizeOriginalFilename(file.getOriginalFilename()));
if (!SUPPORTED_MODEL_EXTENSIONS.contains(extension)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported 3D model type. Allowed: stl, 3mf");
@@ -683,4 +781,14 @@ public class AdminShopProductControllerService {
public record ProductModelDownload(Path path, String filename, String mimeType) {
}
private record LocalizedProductContent(
String defaultName,
String defaultExcerpt,
String defaultDescription,
Map<String, String> names,
Map<String, String> excerpts,
Map<String, String> descriptions
) {
}
}

View File

@@ -99,7 +99,7 @@ public class PublicShopCatalogService {
List<ShopProductSummaryDto> products = productContext.entries().stream()
.filter(entry -> allowedCategoryIds.contains(entry.product().getCategory().getId()))
.filter(entry -> !Boolean.TRUE.equals(featuredOnly) || Boolean.TRUE.equals(entry.product().getIsFeatured()))
.map(entry -> toProductSummaryDto(entry, productContext.productMediaBySlug()))
.map(entry -> toProductSummaryDto(entry, productContext.productMediaBySlug(), language))
.toList();
ShopCategoryDetailDto selectedCategoryDetail = selectedCategory != null
@@ -128,7 +128,7 @@ public class PublicShopCatalogService {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found");
}
return toProductDetailDto(entry, productContext.productMediaBySlug());
return toProductDetailDto(entry, productContext.productMediaBySlug(), language);
}
public ProductModelDownload getProductModelDownload(String slug) {
@@ -348,13 +348,14 @@ public class PublicShopCatalogService {
}
private ShopProductSummaryDto toProductSummaryDto(ProductEntry entry,
Map<String, List<PublicMediaUsageDto>> productMediaBySlug) {
Map<String, List<PublicMediaUsageDto>> productMediaBySlug,
String language) {
List<PublicMediaUsageDto> images = productMediaBySlug.getOrDefault(productMediaUsageKey(entry.product()), List.of());
return new ShopProductSummaryDto(
entry.product().getId(),
entry.product().getSlug(),
entry.product().getName(),
entry.product().getExcerpt(),
entry.product().getNameForLanguage(language),
entry.product().getExcerptForLanguage(language),
entry.product().getIsFeatured(),
entry.product().getSortOrder(),
new ShopCategoryRefDto(
@@ -371,14 +372,15 @@ public class PublicShopCatalogService {
}
private ShopProductDetailDto toProductDetailDto(ProductEntry entry,
Map<String, List<PublicMediaUsageDto>> productMediaBySlug) {
Map<String, List<PublicMediaUsageDto>> productMediaBySlug,
String language) {
List<PublicMediaUsageDto> images = productMediaBySlug.getOrDefault(productMediaUsageKey(entry.product()), List.of());
return new ShopProductDetailDto(
entry.product().getId(),
entry.product().getSlug(),
entry.product().getName(),
entry.product().getExcerpt(),
entry.product().getDescription(),
entry.product().getNameForLanguage(language),
entry.product().getExcerptForLanguage(language),
entry.product().getDescriptionForLanguage(language),
entry.product().getSeoTitle(),
entry.product().getSeoDescription(),
entry.product().getOgTitle(),

View File

@@ -31,6 +31,7 @@ media.storage.root=${MEDIA_STORAGE_ROOT:storage_media}
media.public.base-url=${MEDIA_PUBLIC_BASE_URL:http://localhost:8080/media}
media.ffmpeg.path=${MEDIA_FFMPEG_PATH:ffmpeg}
media.upload.max-file-size-bytes=${MEDIA_UPLOAD_MAX_FILE_SIZE_BYTES:26214400}
shop.model.max-file-size-bytes=${SHOP_MODEL_MAX_FILE_SIZE_BYTES:104857600}
shop.storage.root=${SHOP_STORAGE_ROOT:storage_shop}
shop.cart.cookie.ttl-days=${SHOP_CART_COOKIE_TTL_DAYS:30}
shop.cart.cookie.secure=${SHOP_CART_COOKIE_SECURE:false}