add app API endpoints for categories, platforms, and products; update security configuration
This commit is contained in:
@@ -46,8 +46,7 @@ public class SecurityConfig {
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // autoriser les preflight
|
||||
.requestMatchers("/api/auth/**").permitAll()
|
||||
.requestMatchers("/api/users/**").authenticated()
|
||||
.requestMatchers("/api/brands/**").permitAll()
|
||||
.requestMatchers("/api/platforms/**").permitAll()
|
||||
.requestMatchers("/api/app/**").permitAll()
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/brands")
|
||||
@RequestMapping("/api/app/brands")
|
||||
public class BrandController {
|
||||
|
||||
private final BrandService brandService;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package fr.gameovergne.api.controller.app;
|
||||
|
||||
import fr.gameovergne.api.dto.app.CategoryDTO;
|
||||
import fr.gameovergne.api.mapper.app.CategoryMapper;
|
||||
import fr.gameovergne.api.model.app.Category;
|
||||
import fr.gameovergne.api.service.app.CategoryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/app/categories")
|
||||
public class CategoryController {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
private final CategoryMapper categoryMapper;
|
||||
|
||||
@Autowired
|
||||
public CategoryController(CategoryService categoryService, CategoryMapper categoryMapper) {
|
||||
this.categoryService = categoryService;
|
||||
this.categoryMapper = categoryMapper;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<Category> getAllCategories() {
|
||||
return categoryService.getAllCategories();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Category> getCategoryById(@PathVariable Long id) {
|
||||
return categoryService.getCategoryById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public void saveCategory(@RequestBody CategoryDTO categoryDTO) {
|
||||
categoryService.saveCategory(categoryMapper.fromDto(categoryDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<Category> updateCategory(@PathVariable Long id, @RequestBody CategoryDTO categoryDTO) {
|
||||
Category category = categoryMapper.fromDto(categoryDTO);
|
||||
category.setId(id);
|
||||
return categoryService.getCategoryById(id)
|
||||
.map(existingCategory -> categoryService.updateCategory(category)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build()))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Category> deleteCategoryById(@PathVariable Long id) {
|
||||
return categoryService.deleteCategoryById(id)
|
||||
.map(category -> ResponseEntity.ok().body(category))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package fr.gameovergne.api.controller.app;
|
||||
|
||||
import fr.gameovergne.api.dto.app.ConditionDTO;
|
||||
import fr.gameovergne.api.model.app.Condition;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/app/conditions")
|
||||
public class ConditionController {
|
||||
|
||||
@GetMapping
|
||||
public List<ConditionDTO> list() {
|
||||
return Arrays.stream(Condition.values())
|
||||
.map(condition -> new ConditionDTO(condition.name(), condition.getDisplayName()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/platforms")
|
||||
@RequestMapping("/api/app/platforms")
|
||||
public class PlatformController {
|
||||
|
||||
private final PlatformService platformService;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package fr.gameovergne.api.controller.app;
|
||||
|
||||
import fr.gameovergne.api.dto.app.ProductDTO;
|
||||
import fr.gameovergne.api.mapper.app.ProductMapper;
|
||||
import fr.gameovergne.api.model.app.Product;
|
||||
import fr.gameovergne.api.service.app.ProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/app/products")
|
||||
public class ProductController {
|
||||
|
||||
private final ProductService productService;
|
||||
private final ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
public ProductController(ProductService productService, ProductMapper productMapper) {
|
||||
this.productService = productService;
|
||||
this.productMapper = productMapper;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<Product> getAllProducts() {
|
||||
return productService.getAllProducts();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
|
||||
return productService.getProductById(id)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public void saveProduct(@RequestBody ProductDTO productDTO) {
|
||||
productService.saveProduct(productMapper.fromDto(productDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) {
|
||||
Product product = productMapper.fromDto(productDTO);
|
||||
product.setId(id);
|
||||
return productService.getProductById(id)
|
||||
.map(existingProduct -> productService.updateProduct(product)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build()))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Product> deleteProductById(@PathVariable Long id) {
|
||||
return productService.deleteProductById(id)
|
||||
.map(product -> ResponseEntity.ok().body(product))
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fr.gameovergne.api.dto.app;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CategoryDTO {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fr.gameovergne.api.dto.app;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class ConditionDTO {
|
||||
|
||||
@JsonProperty("name")
|
||||
private final String name;
|
||||
|
||||
@JsonProperty("displayName")
|
||||
private final String displayName;
|
||||
}
|
||||
39
api/src/main/java/fr/gameovergne/api/dto/app/ProductDTO.java
Normal file
39
api/src/main/java/fr/gameovergne/api/dto/app/ProductDTO.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package fr.gameovergne.api.dto.app;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ProductDTO {
|
||||
|
||||
@JsonProperty("title")
|
||||
private String title;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("price")
|
||||
private double price;
|
||||
|
||||
@JsonProperty("quantity")
|
||||
private int quantity;
|
||||
|
||||
@JsonProperty("complete")
|
||||
private boolean complete = false;
|
||||
|
||||
@JsonProperty("manualIncluded")
|
||||
private boolean manualIncluded = false;
|
||||
|
||||
@JsonProperty("category")
|
||||
private CategoryDTO categoryDTO;
|
||||
|
||||
@JsonProperty("platform")
|
||||
private PlatformDTO platformDTO;
|
||||
|
||||
@JsonProperty("condition")
|
||||
private ConditionDTO conditionDTO;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package fr.gameovergne.api.mapper.app;
|
||||
|
||||
import fr.gameovergne.api.dto.app.CategoryDTO;
|
||||
import fr.gameovergne.api.model.app.Category;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CategoryMapper {
|
||||
|
||||
public Category fromDto(CategoryDTO categoryDTO) {
|
||||
if (categoryDTO == null) return null;
|
||||
Category category = new Category();
|
||||
category.setName(categoryDTO.getName());
|
||||
return category;
|
||||
}
|
||||
|
||||
public CategoryDTO toDto(Category category) {
|
||||
if (category == null) return null;
|
||||
CategoryDTO categoryDTO = new CategoryDTO();
|
||||
categoryDTO.setName(category.getName());
|
||||
return categoryDTO;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class PlatformMapper {
|
||||
if (platformDTO == null) return null;
|
||||
Platform platform = new Platform();
|
||||
platform.setName(platformDTO.getName());
|
||||
|
||||
if (platformDTO.getBrandDTO() != null && platformDTO.getBrandDTO().getName() != null) {
|
||||
platform.setBrand(this.brandService.getBrandByName(platformDTO.getBrandDTO().getName()).orElse(null));
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package fr.gameovergne.api.mapper.app;
|
||||
|
||||
import fr.gameovergne.api.dto.app.*;
|
||||
import fr.gameovergne.api.model.app.Brand;
|
||||
import fr.gameovergne.api.model.app.Condition;
|
||||
import fr.gameovergne.api.model.app.Platform;
|
||||
import fr.gameovergne.api.model.app.Product;
|
||||
import fr.gameovergne.api.service.app.BrandService;
|
||||
import fr.gameovergne.api.service.app.CategoryService;
|
||||
import fr.gameovergne.api.service.app.PlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ProductMapper {
|
||||
|
||||
private final CategoryService categoryService;
|
||||
private final BrandService brandService;
|
||||
private final PlatformService platformService;
|
||||
|
||||
public ProductMapper(CategoryService categoryService, BrandService brandService, PlatformService platformService) {
|
||||
this.categoryService = categoryService;
|
||||
this.brandService = brandService;
|
||||
this.platformService = platformService;
|
||||
}
|
||||
|
||||
public Product fromDto(ProductDTO productDTO) {
|
||||
if (productDTO == null) return null;
|
||||
Product product = new Product();
|
||||
product.setTitle(productDTO.getTitle());
|
||||
product.setDescription(productDTO.getDescription());
|
||||
product.setPrice(productDTO.getPrice());
|
||||
product.setQuantity(productDTO.getQuantity());
|
||||
product.setComplete(productDTO.isComplete());
|
||||
product.setManualIncluded(productDTO.isManualIncluded());
|
||||
|
||||
if (productDTO.getCategoryDTO() != null && productDTO.getCategoryDTO().getName() != null) {
|
||||
product.setCategory(this.categoryService.getCategoryByName(productDTO.getCategoryDTO().getName()).orElse(null));
|
||||
} else {
|
||||
product.setCategory(null);
|
||||
}
|
||||
|
||||
// Platform + Brand handling — ensure platform is set on product
|
||||
if (productDTO.getPlatformDTO() != null && productDTO.getPlatformDTO().getName() != null) {
|
||||
String platformName = productDTO.getPlatformDTO().getName();
|
||||
Platform platform = this.platformService.getPlatformByName(platformName)
|
||||
.orElseGet(() -> {
|
||||
Platform p = new Platform();
|
||||
p.setName(platformName);
|
||||
return p;
|
||||
});
|
||||
|
||||
if (productDTO.getPlatformDTO().getBrandDTO() != null && productDTO.getPlatformDTO().getBrandDTO().getName() != null) {
|
||||
String brandName = productDTO.getPlatformDTO().getBrandDTO().getName();
|
||||
Brand brand = this.brandService.getBrandByName(brandName)
|
||||
.orElseGet(() -> {
|
||||
Brand b = new Brand();
|
||||
b.setName(brandName);
|
||||
return b;
|
||||
});
|
||||
platform.setBrand(brand);
|
||||
}
|
||||
|
||||
product.setPlatform(platform);
|
||||
} else {
|
||||
product.setPlatform(null);
|
||||
}
|
||||
|
||||
// Condition handling with null/invalid protection
|
||||
if (productDTO.getConditionDTO() != null && productDTO.getConditionDTO().getName() != null) {
|
||||
try {
|
||||
product.setCondition(Condition.valueOf(productDTO.getConditionDTO().getName()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
product.setCondition(null);
|
||||
}
|
||||
} else {
|
||||
product.setCondition(null);
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
public ProductDTO toDto(Product product) {
|
||||
if (product == null) return null;
|
||||
ProductDTO productDTO = new ProductDTO();
|
||||
productDTO.setTitle(product.getTitle());
|
||||
productDTO.setDescription(product.getDescription());
|
||||
productDTO.setPrice(product.getPrice());
|
||||
productDTO.setQuantity(product.getQuantity());
|
||||
productDTO.setComplete(product.isComplete());
|
||||
productDTO.setManualIncluded(product.isManualIncluded());
|
||||
|
||||
if (product.getCategory() != null) {
|
||||
productDTO.setCategoryDTO(new CategoryDTO(product.getCategory().getName()));
|
||||
} else {
|
||||
productDTO.setCategoryDTO(null);
|
||||
}
|
||||
|
||||
if (product.getPlatform() != null) {
|
||||
Platform platform = product.getPlatform();
|
||||
PlatformDTO platformDTO = new PlatformDTO();
|
||||
platformDTO.setName(platform.getName());
|
||||
|
||||
if (platform.getBrand() != null) {
|
||||
platformDTO.setBrandDTO(new BrandDTO(platform.getBrand().getName()));
|
||||
} else {
|
||||
platformDTO.setBrandDTO(null);
|
||||
}
|
||||
productDTO.setPlatformDTO(platformDTO);
|
||||
} else {
|
||||
productDTO.setPlatformDTO(null);
|
||||
}
|
||||
|
||||
if (product.getCondition() != null) {
|
||||
productDTO.setConditionDTO(new ConditionDTO(product.getCondition().name(), product.getCondition().getDisplayName()));
|
||||
} else {
|
||||
productDTO.setConditionDTO(null);
|
||||
}
|
||||
|
||||
return productDTO;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class Category {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private long id;
|
||||
private Long id;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "category_name", length = 30, unique = true, nullable = false)
|
||||
|
||||
@@ -32,13 +32,21 @@ public class Product {
|
||||
@Column(name = "product_description", length = 500)
|
||||
private String description;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "product_price")
|
||||
private double price;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "product_quantity")
|
||||
private int quantity;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "product_complete")
|
||||
private boolean complete = false;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "product_manual")
|
||||
private boolean manual = false; // Notice
|
||||
private boolean manualIncluded = false; // Notice
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "category_id")
|
||||
|
||||
@@ -6,6 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BrandRepository extends JpaRepository<Brand, Long> {
|
||||
Optional<Brand> findById(Long id);
|
||||
Optional<Brand> findByName(String name);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.gameovergne.api.repository.app;
|
||||
|
||||
import fr.gameovergne.api.model.app.Category;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CategoryRepository extends JpaRepository<Category, Long> {
|
||||
Optional<Category> findByName(String name);
|
||||
}
|
||||
@@ -6,6 +6,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface PlatformRepository extends JpaRepository<Platform, Long> {
|
||||
Optional<Platform> findById(Long id);
|
||||
Optional<Platform> findByName(String name);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package fr.gameovergne.api.repository.app;
|
||||
|
||||
import fr.gameovergne.api.model.app.*;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProductRepository extends JpaRepository<Product, Long> {
|
||||
Optional<Product> findByTitle(String title);
|
||||
Optional<Product> findByCategory(Category category);
|
||||
Optional<Product> findByPlatform(Platform platform);
|
||||
Optional<Product> findByCondition(Condition condition);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package fr.gameovergne.api.service.app;
|
||||
|
||||
import fr.gameovergne.api.model.app.Category;
|
||||
import fr.gameovergne.api.repository.app.CategoryRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class CategoryService {
|
||||
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
@Autowired
|
||||
public CategoryService(CategoryRepository categoryRepository) {
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void saveCategory(Category category) {
|
||||
if (category.getId() == null) {
|
||||
categoryRepository.save(category);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Category> getAllCategories() {
|
||||
return categoryRepository.findAll();
|
||||
}
|
||||
|
||||
public Optional<Category> getCategoryById(Long id) {
|
||||
return categoryRepository.findById(id);
|
||||
}
|
||||
|
||||
public Optional<Category> getCategoryByName(String name) {
|
||||
return categoryRepository.findByName(name);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<Category> updateCategory(Category category) {
|
||||
return categoryRepository.findById(category.getId()).map(existingCategory -> {
|
||||
existingCategory.setName(category.getName());
|
||||
return categoryRepository.save(existingCategory);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<Category> deleteCategoryById(Long id) {
|
||||
Optional<Category> category = categoryRepository.findById(id);
|
||||
category.ifPresent(categoryRepository::delete);
|
||||
return category;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package fr.gameovergne.api.service.app;
|
||||
|
||||
import fr.gameovergne.api.model.app.Product;
|
||||
import fr.gameovergne.api.model.app.Platform;
|
||||
import fr.gameovergne.api.repository.app.ProductRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class ProductService {
|
||||
|
||||
private final ProductRepository productRepository;
|
||||
|
||||
@Autowired
|
||||
public ProductService(ProductRepository productRepository) {
|
||||
this.productRepository = productRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void saveProduct(Product product) {
|
||||
if (product.getId() == null) {
|
||||
productRepository.save(product);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Product> getAllProducts() {
|
||||
return productRepository.findAll();
|
||||
}
|
||||
|
||||
public Optional<Product> getProductById(Long id) {
|
||||
return productRepository.findById(id);
|
||||
}
|
||||
|
||||
public Optional<Product> getProductByName(String name) {
|
||||
return productRepository.findByTitle(name);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<Product> updateProduct(Product product) {
|
||||
return productRepository.findById(product.getId()).map(existingProduct -> {
|
||||
existingProduct.setTitle(product.getTitle());
|
||||
existingProduct.setDescription(product.getDescription());
|
||||
existingProduct.setPrice(product.getPrice());
|
||||
existingProduct.setQuantity(product.getQuantity());
|
||||
existingProduct.setComplete(product.isComplete());
|
||||
existingProduct.setManualIncluded(product.isManualIncluded());
|
||||
existingProduct.setCategory(product.getCategory());
|
||||
existingProduct.setPlatform(product.getPlatform());
|
||||
existingProduct.setCondition(product.getCondition());
|
||||
return productRepository.save(existingProduct);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<Product> deleteProductById(Long id) {
|
||||
Optional<Product> product = productRepository.findById(id);
|
||||
product.ifPresent(productRepository::delete);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user