diff --git a/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java b/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java index a7faf3d..4f6d62f 100644 --- a/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java +++ b/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java @@ -70,7 +70,7 @@ public class SecurityConfig { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration cfg = new CorsConfiguration(); cfg.setAllowedOrigins(List.of("http://localhost:3000")); - cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + cfg.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); cfg.setAllowedHeaders(List.of("Authorization", "Content-Type")); cfg.setExposedHeaders(List.of("Authorization")); cfg.setAllowCredentials(true); diff --git a/src/main/java/group/goforward/battlbuilder/controllers/ProductV1Controller.java b/src/main/java/group/goforward/battlbuilder/controllers/ProductV1Controller.java index ef0a082..7d4fa23 100644 --- a/src/main/java/group/goforward/battlbuilder/controllers/ProductV1Controller.java +++ b/src/main/java/group/goforward/battlbuilder/controllers/ProductV1Controller.java @@ -6,6 +6,9 @@ import group.goforward.battlbuilder.web.dto.ProductSummaryDto; import org.springframework.cache.annotation.Cacheable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import java.util.List; @@ -23,13 +26,14 @@ public class ProductV1Controller { @GetMapping @Cacheable( value = "gunbuilderProductsV1", - key = "#platform + '::' + (#partRoles == null ? 'ALL' : #partRoles.toString())" + key = "#platform + '::' + (#partRoles == null ? 'ALL' : #partRoles.toString()) + '::' + #pageable.pageNumber + '::' + #pageable.pageSize" ) - public List getProducts( + public Page getProducts( @RequestParam(defaultValue = "AR-15") String platform, - @RequestParam(required = false, name = "partRoles") List partRoles + @RequestParam(required = false, name = "partRoles") List partRoles, + @PageableDefault(size = 50) Pageable pageable ) { - return productQueryService.getProducts(platform, partRoles); + return productQueryService.getProductsPage(platform, partRoles, pageable); } @GetMapping("/{id}/offers") diff --git a/src/main/java/group/goforward/battlbuilder/model/Product.java b/src/main/java/group/goforward/battlbuilder/model/Product.java index 0408384..7b20308 100644 --- a/src/main/java/group/goforward/battlbuilder/model/Product.java +++ b/src/main/java/group/goforward/battlbuilder/model/Product.java @@ -8,7 +8,6 @@ import java.util.Comparator; import java.util.Objects; import java.util.Set; import java.util.HashSet; -import group.goforward.battlbuilder.model.PartRoleSource; @Entity @Table(name = "products") @@ -130,6 +129,24 @@ public class Product { public String getCaliberGroup() { return caliberGroup; } public void setCaliberGroup(String caliberGroup) { this.caliberGroup = caliberGroup; } + /* Admin Management */ + @Enumerated(EnumType.STRING) + @Column(name = "visibility", nullable = false) + private ProductVisibility visibility = ProductVisibility.PUBLIC; + + @Column(name = "builder_eligible", nullable = false) + private Boolean builderEligible = true; + + @Column(name = "admin_locked", nullable = false) + private Boolean adminLocked = false; + + @Enumerated(EnumType.STRING) + @Column(name = "status", nullable = false) + private ProductStatus status = ProductStatus.ACTIVE; + + @Column(name = "admin_note") + private String adminNote; + // --- lifecycle hooks --- @PrePersist public void prePersist() { @@ -237,6 +254,22 @@ public class Product { public Boolean getPartRoleLocked() { return partRoleLocked; } public void setPartRoleLocked(Boolean partRoleLocked) { this.partRoleLocked = partRoleLocked; } + + // --- Admin Getters/setters + public ProductVisibility getVisibility() { return visibility; } + public void setVisibility(ProductVisibility visibility) { this.visibility = visibility; } + + public Boolean getBuilderEligible() { return builderEligible; } + public void setBuilderEligible(Boolean builderEligible) { this.builderEligible = builderEligible; } + + public Boolean getAdminLocked() { return adminLocked; } + public void setAdminLocked(Boolean adminLocked) { this.adminLocked = adminLocked; } + + public ProductStatus getStatus() { return status; } + public void setStatus(ProductStatus status) { this.status = status; } + + public String getAdminNote() { return adminNote; } + public void setAdminNote(String adminNote) { this.adminNote = adminNote; } // --- computed helpers --- public BigDecimal getBestOfferPrice() { diff --git a/src/main/java/group/goforward/battlbuilder/model/ProductStatus.java b/src/main/java/group/goforward/battlbuilder/model/ProductStatus.java new file mode 100644 index 0000000..c45bc80 --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/model/ProductStatus.java @@ -0,0 +1,7 @@ +package group.goforward.battlbuilder.model; + +public enum ProductStatus { + ACTIVE, + DISABLED, + ARCHIVED +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/model/ProductVisibility.java b/src/main/java/group/goforward/battlbuilder/model/ProductVisibility.java new file mode 100644 index 0000000..d286c5f --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/model/ProductVisibility.java @@ -0,0 +1,6 @@ +package group.goforward.battlbuilder.model; + +public enum ProductVisibility { + PUBLIC, + HIDDEN +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/repos/ProductRepository.java b/src/main/java/group/goforward/battlbuilder/repos/ProductRepository.java index aff9e87..dfa53e7 100644 --- a/src/main/java/group/goforward/battlbuilder/repos/ProductRepository.java +++ b/src/main/java/group/goforward/battlbuilder/repos/ProductRepository.java @@ -10,12 +10,16 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import java.util.Collection; import java.util.List; import java.util.Map; -public interface ProductRepository extends JpaRepository { +public interface ProductRepository + extends JpaRepository, + JpaSpecificationExecutor { + /** @@ -238,6 +242,20 @@ where po.product_id = p.id List findByMerchantIdAndRawCategoryKey(@Param("merchantId") Integer merchantId, @Param("rawCategoryKey") String rawCategoryKey); + + @Query("SELECT p FROM Product p JOIN FETCH p.brand b WHERE p.deletedAt IS NULL") + Page findAllWithBrand(Pageable pageable); + + @Query("SELECT p FROM Product p JOIN FETCH p.brand b WHERE p.platform = :platform AND p.deletedAt IS NULL") + Page findByPlatformWithBrand(String platform, Pageable pageable); + + @Query("SELECT p FROM Product p JOIN FETCH p.brand b WHERE p.partRole IN :roles AND p.deletedAt IS NULL") + Page findByPartRoleInWithBrand(List roles, Pageable pageable); + + @Query("SELECT p FROM Product p JOIN FETCH p.brand b WHERE p.platform = :platform AND p.partRole IN :roles AND p.deletedAt IS NULL") + Page findByPlatformAndPartRoleInWithBrand(String platform, List roles, Pageable pageable); + + // ------------------------------------------------- // Admin import-status dashboard (summary) // ------------------------------------------------- diff --git a/src/main/java/group/goforward/battlbuilder/services/ProductQueryService.java b/src/main/java/group/goforward/battlbuilder/services/ProductQueryService.java index 98043d7..0ca20d1 100644 --- a/src/main/java/group/goforward/battlbuilder/services/ProductQueryService.java +++ b/src/main/java/group/goforward/battlbuilder/services/ProductQueryService.java @@ -5,6 +5,9 @@ import group.goforward.battlbuilder.web.dto.ProductSummaryDto; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + public interface ProductQueryService { List getProducts(String platform, List partRoles); @@ -12,4 +15,6 @@ public interface ProductQueryService { List getOffersForProduct(Integer productId); ProductSummaryDto getProductById(Integer productId); + + Page getProductsPage(String platform, List partRoles, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/services/admin/AdminProductService.java b/src/main/java/group/goforward/battlbuilder/services/admin/AdminProductService.java new file mode 100644 index 0000000..9cbf4c7 --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/services/admin/AdminProductService.java @@ -0,0 +1,18 @@ +package group.goforward.battlbuilder.services.admin; + +import group.goforward.battlbuilder.web.dto.admin.AdminProductSearchRequest; +import group.goforward.battlbuilder.web.dto.admin.ProductAdminRowDto; +import group.goforward.battlbuilder.web.dto.admin.ProductBulkUpdateRequest; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface AdminProductService { + + Page search( + AdminProductSearchRequest request, + Pageable pageable + ); + + int bulkUpdate(ProductBulkUpdateRequest request); +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/services/admin/impl/AdminProductServiceImpl.java b/src/main/java/group/goforward/battlbuilder/services/admin/impl/AdminProductServiceImpl.java new file mode 100644 index 0000000..1f242aa --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/services/admin/impl/AdminProductServiceImpl.java @@ -0,0 +1,65 @@ +package group.goforward.battlbuilder.services.admin.impl; + +import group.goforward.battlbuilder.model.Product; +import group.goforward.battlbuilder.repos.ProductRepository; +import group.goforward.battlbuilder.services.admin.AdminProductService; +import group.goforward.battlbuilder.specs.ProductSpecifications; +import group.goforward.battlbuilder.web.dto.admin.AdminProductSearchRequest; +import group.goforward.battlbuilder.web.dto.admin.ProductAdminRowDto; +import group.goforward.battlbuilder.web.dto.admin.ProductBulkUpdateRequest; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +public class AdminProductServiceImpl implements AdminProductService { + + private final ProductRepository productRepository; + + public AdminProductServiceImpl(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + @Override + public Page search( + AdminProductSearchRequest request, + Pageable pageable + ) { + Specification spec = + ProductSpecifications.adminSearch(request); + + return productRepository + .findAll(spec, pageable) + .map(ProductAdminRowDto::fromEntity); + } + + @Override + public int bulkUpdate(ProductBulkUpdateRequest request) { + var products = productRepository.findAllById(request.getProductIds()); + + products.forEach(p -> { + if (request.getVisibility() != null) { + p.setVisibility(request.getVisibility()); + } + if (request.getStatus() != null) { + p.setStatus(request.getStatus()); + } + if (request.getBuilderEligible() != null) { + p.setBuilderEligible(request.getBuilderEligible()); + } + if (request.getAdminLocked() != null) { + p.setAdminLocked(request.getAdminLocked()); + } + if (request.getAdminNote() != null) { + p.setAdminNote(request.getAdminNote()); + } + }); + + productRepository.saveAll(products); + return products.size(); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/services/impl/ProductQueryServiceImpl.java b/src/main/java/group/goforward/battlbuilder/services/impl/ProductQueryServiceImpl.java index d4cf2de..49f75ca 100644 --- a/src/main/java/group/goforward/battlbuilder/services/impl/ProductQueryServiceImpl.java +++ b/src/main/java/group/goforward/battlbuilder/services/impl/ProductQueryServiceImpl.java @@ -13,6 +13,15 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.stream.Collectors; +import java.util.Collections; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; @Service public class ProductQueryServiceImpl implements ProductQueryService { @@ -69,6 +78,57 @@ public class ProductQueryServiceImpl implements ProductQueryService { .toList(); } + @Override + public Page getProductsPage(String platform, List partRoles, Pageable pageable) { + final boolean allPlatforms = platform != null && platform.equalsIgnoreCase("ALL"); + + Page productPage; + + if (partRoles == null || partRoles.isEmpty()) { + productPage = allPlatforms + ? productRepository.findAllWithBrand(pageable) + : productRepository.findByPlatformWithBrand(platform, pageable); + } else { + productPage = allPlatforms + ? productRepository.findByPartRoleInWithBrand(partRoles, pageable) + : productRepository.findByPlatformAndPartRoleInWithBrand(platform, partRoles, pageable); + } + + List products = productPage.getContent(); + if (products.isEmpty()) { + return Page.empty(pageable); + } + + List productIds = products.stream().map(Product::getId).toList(); + + // Only fetch offers for THIS PAGE of products + List allOffers = productOfferRepository.findByProduct_IdIn(productIds); + + Map> offersByProductId = allOffers.stream() + .filter(o -> o.getProduct() != null && o.getProduct().getId() != null) + .collect(Collectors.groupingBy(o -> o.getProduct().getId())); + + List dtos = products.stream() + .map(p -> { + List offersForProduct = + offersByProductId.getOrDefault(p.getId(), Collections.emptyList()); + + ProductOffer bestOffer = pickBestOffer(offersForProduct); + + BigDecimal price = bestOffer != null ? bestOffer.getEffectivePrice() : null; + String buyUrl = bestOffer != null ? bestOffer.getBuyUrl() : null; + + return ProductMapper.toSummary(p, price, buyUrl); + }) + .toList(); + + return new PageImpl<>(dtos, pageable, productPage.getTotalElements()); + } + + // + // Product Offers + // + @Override public List getOffersForProduct(Integer productId) { // ✅ canonical repo method diff --git a/src/main/java/group/goforward/battlbuilder/specs/ProductSpecifications.java b/src/main/java/group/goforward/battlbuilder/specs/ProductSpecifications.java new file mode 100644 index 0000000..598bc87 --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/specs/ProductSpecifications.java @@ -0,0 +1,113 @@ +package group.goforward.battlbuilder.specs; + +import group.goforward.battlbuilder.model.ImportStatus; +import group.goforward.battlbuilder.model.Product; +import group.goforward.battlbuilder.model.ProductStatus; +import group.goforward.battlbuilder.model.ProductVisibility; +import group.goforward.battlbuilder.web.dto.admin.AdminProductSearchRequest; + +import org.springframework.data.jpa.domain.Specification; + +import jakarta.persistence.criteria.Predicate; +import java.util.ArrayList; +import java.util.List; + +public final class ProductSpecifications { + + private ProductSpecifications() {} + + public static Specification adminSearch(AdminProductSearchRequest req) { + return (root, query, cb) -> { + List predicates = new ArrayList<>(); + + // Always exclude soft-deleted + predicates.add(cb.isNull(root.get("deletedAt"))); + + if (req == null) { + return cb.and(predicates.toArray(new Predicate[0])); + } + + if (hasText(req.getPlatform())) { + predicates.add(cb.equal(root.get("platform"), req.getPlatform())); + } + + if (hasText(req.getPartRole())) { + // If you normalize partRole elsewhere, keep it consistent here. + predicates.add(cb.equal(cb.lower(root.get("partRole")), req.getPartRole().toLowerCase())); + } + + if (req.getImportStatus() != null) { + predicates.add(cb.equal(root.get("importStatus"), req.getImportStatus())); + } + + if (req.getVisibility() != null) { + predicates.add(cb.equal(root.get("visibility"), req.getVisibility())); + } + + if (req.getStatus() != null) { + predicates.add(cb.equal(root.get("status"), req.getStatus())); + } + + if (req.getBuilderEligible() != null) { + predicates.add(cb.equal(root.get("builderEligible"), req.getBuilderEligible())); + } + + if (req.getAdminLocked() != null) { + predicates.add(cb.equal(root.get("adminLocked"), req.getAdminLocked())); + } + + if (hasText(req.getQ())) { + String like = "%" + req.getQ().trim().toLowerCase() + "%"; + + Predicate name = cb.like(cb.lower(root.get("name")), like); + Predicate slug = cb.like(cb.lower(root.get("slug")), like); + + // mpn/upc are nullable + Predicate mpn = cb.like(cb.lower(cb.coalesce(root.get("mpn"), "")), like); + Predicate upc = cb.like(cb.lower(cb.coalesce(root.get("upc"), "")), like); + + predicates.add(cb.or(name, slug, mpn, upc)); + } + + return cb.and(predicates.toArray(new Predicate[0])); + }; + } + + // --- reusable small specs (optional, but nice to have) --- + + public static Specification notDeleted() { + return (root, query, cb) -> cb.isNull(root.get("deletedAt")); + } + + public static Specification platform(String platform) { + return (root, query, cb) -> cb.equal(root.get("platform"), platform); + } + + public static Specification partRoleEquals(String partRole) { + return (root, query, cb) -> cb.equal(cb.lower(root.get("partRole")), partRole.toLowerCase()); + } + + public static Specification importStatus(ImportStatus status) { + return (root, query, cb) -> cb.equal(root.get("importStatus"), status); + } + + public static Specification visibility(ProductVisibility visibility) { + return (root, query, cb) -> cb.equal(root.get("visibility"), visibility); + } + + public static Specification status(ProductStatus status) { + return (root, query, cb) -> cb.equal(root.get("status"), status); + } + + public static Specification builderEligible(Boolean builderEligible) { + return (root, query, cb) -> cb.equal(root.get("builderEligible"), builderEligible); + } + + public static Specification adminLocked(Boolean adminLocked) { + return (root, query, cb) -> cb.equal(root.get("adminLocked"), adminLocked); + } + + private static boolean hasText(String s) { + return s != null && !s.trim().isEmpty(); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/web/admin/AdminProductController.java b/src/main/java/group/goforward/battlbuilder/web/admin/AdminProductController.java new file mode 100644 index 0000000..df5e539 --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/web/admin/AdminProductController.java @@ -0,0 +1,45 @@ +package group.goforward.battlbuilder.web.admin; + +import group.goforward.battlbuilder.services.admin.AdminProductService; +import group.goforward.battlbuilder.web.dto.admin.AdminProductSearchRequest; +import group.goforward.battlbuilder.web.dto.admin.ProductBulkUpdateRequest; +import group.goforward.battlbuilder.web.dto.admin.ProductAdminRowDto; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/admin/products") +public class AdminProductController { + + private final AdminProductService adminProductService; + + public AdminProductController(AdminProductService adminProductService) { + this.adminProductService = adminProductService; + } + + /** + * Admin product list (paged + filterable) + */ + @GetMapping + public Page search( + AdminProductSearchRequest request, + Pageable pageable + ) { + return adminProductService.search(request, pageable); + } + + /** + * Bulk admin actions (disable, hide, lock, etc.) + */ + @PatchMapping("/bulk") + public Map bulkUpdate( + @RequestBody ProductBulkUpdateRequest request + ) { + int updated = adminProductService.bulkUpdate(request); + return Map.of("updatedCount", updated); + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/web/dto/admin/AdminProductSearchRequest.java b/src/main/java/group/goforward/battlbuilder/web/dto/admin/AdminProductSearchRequest.java new file mode 100644 index 0000000..fde9bfe --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/web/dto/admin/AdminProductSearchRequest.java @@ -0,0 +1,49 @@ +package group.goforward.battlbuilder.web.dto.admin; + +import group.goforward.battlbuilder.model.ImportStatus; +import group.goforward.battlbuilder.model.ProductStatus; +import group.goforward.battlbuilder.model.ProductVisibility; + +/** + * Bound from query params on: + * GET /api/v1/admin/products?platform=AR-15&q=mini&visibility=PUBLIC... + */ +public class AdminProductSearchRequest { + + private String q; + private String platform; + private String partRole; + + private ImportStatus importStatus; + private ProductVisibility visibility; + private ProductStatus status; + + private Boolean builderEligible; + private Boolean adminLocked; + + // --- getters/setters --- + + public String getQ() { return q; } + public void setQ(String q) { this.q = q; } + + public String getPlatform() { return platform; } + public void setPlatform(String platform) { this.platform = platform; } + + public String getPartRole() { return partRole; } + public void setPartRole(String partRole) { this.partRole = partRole; } + + public ImportStatus getImportStatus() { return importStatus; } + public void setImportStatus(ImportStatus importStatus) { this.importStatus = importStatus; } + + public ProductVisibility getVisibility() { return visibility; } + public void setVisibility(ProductVisibility visibility) { this.visibility = visibility; } + + public ProductStatus getStatus() { return status; } + public void setStatus(ProductStatus status) { this.status = status; } + + public Boolean getBuilderEligible() { return builderEligible; } + public void setBuilderEligible(Boolean builderEligible) { this.builderEligible = builderEligible; } + + public Boolean getAdminLocked() { return adminLocked; } + public void setAdminLocked(Boolean adminLocked) { this.adminLocked = adminLocked; } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/web/dto/admin/ProductAdminRowDto.java b/src/main/java/group/goforward/battlbuilder/web/dto/admin/ProductAdminRowDto.java new file mode 100644 index 0000000..f99c62f --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/web/dto/admin/ProductAdminRowDto.java @@ -0,0 +1,203 @@ +package group.goforward.battlbuilder.web.dto.admin; + +import group.goforward.battlbuilder.model.ImportStatus; +import group.goforward.battlbuilder.model.Product; +import group.goforward.battlbuilder.model.ProductStatus; +import group.goforward.battlbuilder.model.ProductVisibility; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.UUID; + +/** + * Lightweight row for the admin products table. + * Avoids fetching offers unless you explicitly want it. + */ +public class ProductAdminRowDto { + + private Integer id; + private UUID uuid; + + private String name; + private String slug; + + private String platform; + private String partRole; + + private String brandName; + + private ImportStatus importStatus; + + private ProductVisibility visibility; + private ProductStatus status; + private Boolean builderEligible; + private Boolean adminLocked; + + private String adminNote; + + private String mainImageUrl; + + private Instant createdAt; + private Instant updatedAt; + + + public static ProductAdminRowDto fromEntity(Product p) { + ProductAdminRowDto dto = new ProductAdminRowDto(); + + dto.id = p.getId(); + dto.uuid = p.getUuid(); + + dto.name = p.getName(); + dto.slug = p.getSlug(); + + dto.platform = p.getPlatform(); + dto.partRole = p.getPartRole(); + + dto.brandName = (p.getBrand() != null) ? p.getBrand().getName() : null; + + dto.importStatus = p.getImportStatus(); + + dto.visibility = p.getVisibility(); + dto.status = p.getStatus(); + dto.builderEligible = p.getBuilderEligible(); + dto.adminLocked = p.getAdminLocked(); + + dto.adminNote = p.getAdminNote(); + dto.mainImageUrl = p.getMainImageUrl(); + + dto.createdAt = p.getCreatedAt(); + dto.updatedAt = p.getUpdatedAt(); + + return dto; + } + + // --- getters/setters --- + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSlug() { + return slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getPartRole() { + return partRole; + } + + public void setPartRole(String partRole) { + this.partRole = partRole; + } + + public String getBrandName() { + return brandName; + } + + public void setBrandName(String brandName) { + this.brandName = brandName; + } + + public ImportStatus getImportStatus() { + return importStatus; + } + + public void setImportStatus(ImportStatus importStatus) { + this.importStatus = importStatus; + } + + public ProductVisibility getVisibility() { + return visibility; + } + + public void setVisibility(ProductVisibility visibility) { + this.visibility = visibility; + } + + public ProductStatus getStatus() { + return status; + } + + public void setStatus(ProductStatus status) { + this.status = status; + } + + public Boolean getBuilderEligible() { + return builderEligible; + } + + public void setBuilderEligible(Boolean builderEligible) { + this.builderEligible = builderEligible; + } + + public Boolean getAdminLocked() { + return adminLocked; + } + + public void setAdminLocked(Boolean adminLocked) { + this.adminLocked = adminLocked; + } + + public String getAdminNote() { + return adminNote; + } + + public void setAdminNote(String adminNote) { + this.adminNote = adminNote; + } + + public String getMainImageUrl() { + return mainImageUrl; + } + + public void setMainImageUrl(String mainImageUrl) { + this.mainImageUrl = mainImageUrl; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Instant updatedAt) { + this.updatedAt = updatedAt; + } +} \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/web/dto/admin/ProductBulkUpdateRequest.java b/src/main/java/group/goforward/battlbuilder/web/dto/admin/ProductBulkUpdateRequest.java new file mode 100644 index 0000000..e235657 --- /dev/null +++ b/src/main/java/group/goforward/battlbuilder/web/dto/admin/ProductBulkUpdateRequest.java @@ -0,0 +1,39 @@ +package group.goforward.battlbuilder.web.dto.admin; + +import group.goforward.battlbuilder.model.ProductStatus; +import group.goforward.battlbuilder.model.ProductVisibility; + +import java.util.Set; + +public class ProductBulkUpdateRequest { + + private Set productIds; + + private ProductVisibility visibility; + private ProductStatus status; + + private Boolean builderEligible; + private Boolean adminLocked; + + private String adminNote; + + // --- getters/setters --- + + public Set getProductIds() { return productIds; } + public void setProductIds(Set productIds) { this.productIds = productIds; } + + public ProductVisibility getVisibility() { return visibility; } + public void setVisibility(ProductVisibility visibility) { this.visibility = visibility; } + + public ProductStatus getStatus() { return status; } + public void setStatus(ProductStatus status) { this.status = status; } + + public Boolean getBuilderEligible() { return builderEligible; } + public void setBuilderEligible(Boolean builderEligible) { this.builderEligible = builderEligible; } + + public Boolean getAdminLocked() { return adminLocked; } + public void setAdminLocked(Boolean adminLocked) { this.adminLocked = adminLocked; } + + public String getAdminNote() { return adminNote; } + public void setAdminNote(String adminNote) { this.adminNote = adminNote; } +} \ No newline at end of file