Merge branch 'develop' of ssh://gitea.gofwd.group:2225/Forward_Group/ballistic-builder-spring into develop

This commit is contained in:
2025-12-12 08:06:34 -05:00
3 changed files with 0 additions and 318 deletions

View File

@@ -1,83 +0,0 @@
package group.goforward.battlbuilder.controllers;
import group.goforward.battlbuilder.services.GunbuilderProductService;
import group.goforward.battlbuilder.web.dto.GunbuilderProductDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/gunbuilder")
public class GunbuilderProductController {
private final GunbuilderProductService gunbuilderProductService;
public GunbuilderProductController(GunbuilderProductService gunbuilderProductService) {
this.gunbuilderProductService = gunbuilderProductService;
System.out.println(">>> GunbuilderProductController initialized");
}
// 🔹 sanity check: is this controller even mapped?
@GetMapping("/ping")
public Map<String, String> ping() {
return Map.of("status", "ok", "source", "GunbuilderProductController");
}
// 🔹 super-dumb test: no DB, no service, just prove the route works
@GetMapping("/test-products")
public Map<String, Object> testProducts(@RequestParam String platform) {
System.out.println(">>> /api/gunbuilder/test-products hit for platform=" + platform);
Map<String, Object> m = new java.util.HashMap<>();
m.put("platform", platform);
m.put("note", "test endpoint only");
m.put("ok", true);
return m;
}
/**
* List products for the builder UI.
*
* Examples:
* GET /api/gunbuilder/products?platform=AR-15
* GET /api/gunbuilder/products?platform=AR-15&partRoles=LOWER_RECEIVER_STRIPPED&partRoles=LOWER_RECEIVER_COMPLETE
*/
@GetMapping("/products")
public List<GunbuilderProductDto> listProducts(
@RequestParam String platform,
@RequestParam(required = false) List<String> partRoles
) {
return gunbuilderProductService.listGunbuilderProducts(platform, partRoles);
}
@GetMapping("/products/by-category")
public List<GunbuilderProductDto> listProductsByCategory(
@RequestParam String platform,
@RequestParam String categorySlug
) {
return gunbuilderProductService.listGunbuilderProductsByCategory(platform, categorySlug);
}
// 🔹 DB test: hit repo via service and return a tiny view of products
@GetMapping("/test-products-db")
public List<Map<String, Object>> testProductsDb(@RequestParam String platform) {
System.out.println(">>> /api/gunbuilder/test-products-db hit for platform=" + platform);
var products = gunbuilderProductService.getSampleProducts(platform);
return products.stream()
.map(p -> {
Map<String, Object> m = new java.util.HashMap<>();
m.put("id", p.getId());
m.put("name", p.getName());
m.put("brand", p.getBrand() != null ? p.getBrand().getName() : null);
m.put("partRole", p.getPartRole());
return m;
})
.toList();
}
}

View File

@@ -1,182 +0,0 @@
package group.goforward.battlbuilder.services;
import group.goforward.battlbuilder.model.PartCategory;
import group.goforward.battlbuilder.model.Product;
import group.goforward.battlbuilder.repos.ProductRepository;
import group.goforward.battlbuilder.web.dto.GunbuilderProductDto;
import org.springframework.stereotype.Service;
import group.goforward.battlbuilder.model.PartRoleMapping;
import group.goforward.battlbuilder.repos.PartRoleMappingRepository;
import group.goforward.battlbuilder.model.ImportStatus;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Optional;
@Service
public class GunbuilderProductService {
private final ProductRepository productRepository;
private final PartCategoryResolverService partCategoryResolverService;
private final PartRoleMappingRepository partRoleMappingRepository;
public GunbuilderProductService(
ProductRepository productRepository,
PartCategoryResolverService partCategoryResolverService,
PartRoleMappingRepository partRoleMappingRepository
) {
this.productRepository = productRepository;
this.partCategoryResolverService = partCategoryResolverService;
this.partRoleMappingRepository = partRoleMappingRepository;
}
/**
* Main builder endpoint.
* For now we ONLY support calls that provide partRoles,
* to avoid pulling the entire catalog into memory.
*/
public List<GunbuilderProductDto> listGunbuilderProducts(String platform, List<String> partRoles) {
System.out.println(">>> GB: listGunbuilderProducts platform=" + platform
+ ", partRoles=" + partRoles);
if (partRoles == null || partRoles.isEmpty()) {
System.out.println(">>> GB: no partRoles provided, returning empty list");
return List.of();
}
List<Product> products =
productRepository.findForGunbuilderByPlatformAndPartRoles(
platform,
partRoles,
ImportStatus.MAPPED
);
System.out.println(">>> GB: repo returned " + products.size() + " products");
Map<String, PartCategory> categoryCache = new HashMap<>();
return products.stream()
.map(p -> {
PartCategory cat = categoryCache.computeIfAbsent(
p.getPartRole(),
role -> partCategoryResolverService
.resolveForPlatformAndPartRole(platform, role)
.orElse(null)
);
if (cat == null) {
System.out.println(">>> GB: NO CATEGORY for platform=" + platform
+ ", partRole=" + p.getPartRole()
+ ", productId=" + p.getId());
} else {
System.out.println(">>> GB: CATEGORY for productId=" + p.getId()
+ " -> slug=" + cat.getSlug()
+ ", group=" + cat.getGroupName());
}
// TEMP: do NOT drop products if category is null.
// Just mark them as "unmapped" so we can see them in the JSON.
String categorySlug = (cat != null) ? cat.getSlug() : "unmapped";
String categoryGroup = (cat != null) ? cat.getGroupName() : "Unmapped";
return new GunbuilderProductDto(
p.getId(),
p.getName(),
p.getBrand().getName(),
platform,
p.getPartRole(),
p.getBestOfferPrice(),
p.getMainImageUrl(),
p.getBestOfferBuyUrl(),
categorySlug,
categoryGroup
);
})
.toList();
}
public List<GunbuilderProductDto> listGunbuilderProductsByCategory(
String platform,
String categorySlug
) {
System.out.println(">>> GB: listGunbuilderProductsByCategory platform=" + platform
+ ", categorySlug=" + categorySlug);
if (platform == null || platform.isBlank()
|| categorySlug == null || categorySlug.isBlank()) {
System.out.println(">>> GB: missing platform or categorySlug, returning empty list");
return List.of();
}
List<PartRoleMapping> mappings =
partRoleMappingRepository.findByPlatformAndPartCategory_SlugAndDeletedAtIsNull(
platform,
categorySlug
);
List<String> partRoles = mappings.stream()
.map(PartRoleMapping::getPartRole)
.distinct()
.toList();
System.out.println(">>> GB: resolved " + partRoles.size()
+ " partRoles for categorySlug=" + categorySlug + " -> " + partRoles);
if (partRoles.isEmpty()) {
return List.of();
}
// Reuse the existing method that already does all the DTO + category resolution logic
return listGunbuilderProducts(platform, partRoles);
}
public Optional<GunbuilderProductDto> getGunbuilderProductById(String id) {
if (id == null || id.isBlank()) {
return Optional.empty();
}
Integer numericId;
try {
numericId = Integer.parseInt(id);
} catch (NumberFormatException ex) {
return Optional.empty();
}
return productRepository.findById(numericId)
.map(p -> {
String platform = p.getPlatform();
PartCategory cat = partCategoryResolverService
.resolveForPlatformAndPartRole(platform, p.getPartRole())
.orElse(null);
String categorySlug = (cat != null) ? cat.getSlug() : "unmapped";
String categoryGroup = (cat != null) ? cat.getGroupName() : "Unmapped";
return new GunbuilderProductDto(
p.getId(),
p.getName(),
p.getBrand().getName(),
platform,
p.getPartRole(),
p.getBestOfferPrice(),
p.getMainImageUrl(),
p.getBestOfferBuyUrl(),
categorySlug,
categoryGroup
);
});
}
/**
* Tiny helper used ONLY by /test-products-db to prove DB wiring.
*/
public List<Product> getSampleProducts(String platform) {
// You already have this wired via ProductRepository.findTop5ByPlatformWithBrand
// If that method exists, keep using it; if not, you can stub a tiny query here.
return productRepository.findTop5ByPlatformWithBrand(platform);
}
}

View File

@@ -1,53 +0,0 @@
package group.goforward.battlbuilder.web.dto;
import java.math.BigDecimal;
public class GunbuilderProductDto {
private Integer id;
private String name;
private String brand;
private String platform;
private String partRole;
private BigDecimal price;
private String imageUrl;
private String buyUrl;
private String categorySlug;
private String categoryGroup;
public GunbuilderProductDto(
Integer id,
String name,
String brand,
String platform,
String partRole,
BigDecimal price,
String imageUrl,
String buyUrl,
String categorySlug,
String categoryGroup
) {
this.id = id;
this.name = name;
this.brand = brand;
this.platform = platform;
this.partRole = partRole;
this.price = price;
this.imageUrl = imageUrl;
this.buyUrl = buyUrl;
this.categorySlug = categorySlug;
this.categoryGroup = categoryGroup;
}
// --- Getters only (DTOs are read-only in most cases) ---
public Integer getId() { return id; }
public String getName() { return name; }
public String getBrand() { return brand; }
public String getPlatform() { return platform; }
public String getPartRole() { return partRole; }
public BigDecimal getPrice() { return price; }
public String getImageUrl() { return imageUrl; }
public String getBuyUrl() { return buyUrl; }
public String getCategorySlug() { return categorySlug; }
public String getCategoryGroup() { return categoryGroup; }
}