moving and a shaking up this build

This commit is contained in:
Don Strawsburg
2026-01-16 12:54:33 -05:00
parent 65b91df22c
commit 3f33d944e2
30 changed files with 203 additions and 275 deletions

View File

@@ -13,7 +13,7 @@ public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// Must match the @Cacheable value(s) used in controllers/services.
// ProductV1Controller uses: "gunbuilderProductsV1"
// ProductController uses: "gunbuilderProductsV1"
return new ConcurrentMapCacheManager(
"gunbuilderProductsV1",
"gunbuilderProducts" // keep if anything else still references it

View File

@@ -1,35 +1,113 @@
package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.mapper.BuildMapper;
import group.goforward.battlbuilder.model.Build;
import group.goforward.battlbuilder.repos.build.BuildRepository;
import group.goforward.battlbuilder.web.dto.build.BuildDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping({"/api/builds", "/api/v1fix/builds"})
public class BuildController {
@Autowired
private BuildRepository repo;
// @Autowired
// private BuildsService service;
//@Cacheable(value="getAllBuilds")
@GetMapping("/all")
public ResponseEntity<List<BuildDto>> getAll() {
List<Build> builds = repo.findAll();
return ResponseEntity.ok(BuildMapper.toDtoList(builds));
}
@GetMapping("/{id}")
public ResponseEntity<BuildDto> getAllBuildsById(@PathVariable Integer id) {
return repo.findById(id)
.map(build -> ResponseEntity.ok(BuildMapper.toDto(build)))
.orElse(ResponseEntity.notFound().build());
}
}
package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.mapper.BuildMapper;
import group.goforward.battlbuilder.model.Build;
import group.goforward.battlbuilder.repos.build.BuildRepository;
import group.goforward.battlbuilder.services.BuildService;
import group.goforward.battlbuilder.web.dto.build.BuildDto;
import group.goforward.battlbuilder.web.dto.build.BuildFeedCardDto;
import group.goforward.battlbuilder.web.dto.build.BuildSummaryDto;
import group.goforward.battlbuilder.web.dto.mapping.UpdateBuildRequestDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@CrossOrigin
@RestController
@RequestMapping("/api/v1/builds")
public class BuildController {
@Autowired
private BuildRepository repo;
private final BuildService buildService;
public BuildController(BuildService buildService) {
this.buildService = buildService;
}
/**
* Public builds feed for /builds page.
* GET /api/v1/builds?limit=50
*/
@GetMapping
public ResponseEntity<List<BuildFeedCardDto>> listPublicBuilds(
@RequestParam(name = "limit", required = false, defaultValue = "50") Integer limit
) {
return ResponseEntity.ok(buildService.listPublicBuilds(limit == null ? 50 : limit));
}
/**
* Public build detail for /builds/{uuid}
* GET /api/v1/builds/{uuid}
*/
@GetMapping("/{uuid}")
public ResponseEntity<BuildDto> getPublicBuild(@PathVariable("uuid") UUID uuid) {
return ResponseEntity.ok(buildService.getPublicBuild(uuid));
}
/**
* Vault builds (authenticated user).
* GET /api/v1/builds/me?limit=100
*/
@GetMapping("/me")
public ResponseEntity<List<BuildSummaryDto>> listMyBuilds(
@RequestParam(name = "limit", required = false, defaultValue = "100") Integer limit
) {
return ResponseEntity.ok(buildService.listMyBuilds(limit == null ? 100 : limit));
}
/**
* Load a single build (Vault edit + Builder ?load=uuid).
* GET /api/v1/builds/me/{uuid}
*/
@GetMapping("/me/{uuid}")
public ResponseEntity<BuildDto> getMyBuild(@PathVariable("uuid") UUID uuid) {
return ResponseEntity.ok(buildService.getMyBuild(uuid));
}
/**
* Create a NEW build in Vault (Save As…).
* POST /api/v1/builds/me
*/
@PostMapping("/me")
public ResponseEntity<BuildDto> createMyBuild(@RequestBody UpdateBuildRequestDto req) {
return ResponseEntity.ok(buildService.createMyBuild(req));
}
/**
* Update build (authenticated user; must own build eventually).
* PUT /api/v1/builds/me/{uuid}
*/
@PutMapping("/me/{uuid}")
public ResponseEntity<BuildDto> updateMyBuild(
@PathVariable("uuid") UUID uuid,
@RequestBody UpdateBuildRequestDto req
) {
return ResponseEntity.ok(buildService.updateMyBuild(uuid, req));
}
/**
* Delete a build (authenticated user; must own build).
* DELETE /api/v1/builds/me/{uuid}
*/
@DeleteMapping("/me/{uuid}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteMyBuild(@PathVariable("uuid") UUID uuid) {
buildService.deleteMyBuild(uuid);
}
@GetMapping("/all")
public ResponseEntity<List<BuildDto>> getAll() {
List<Build> builds = repo.findAll();
return ResponseEntity.ok(BuildMapper.toDtoList(builds));
}
@GetMapping("/{id}")
public ResponseEntity<BuildDto> getAllBuildsById(@PathVariable Integer id) {
return repo.findById(id)
.map(build -> ResponseEntity.ok(BuildMapper.toDto(build)))
.orElse(ResponseEntity.notFound().build());
}
}

View File

@@ -1,96 +0,0 @@
package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.services.BuildService;
import group.goforward.battlbuilder.web.dto.build.BuildDto;
import group.goforward.battlbuilder.web.dto.build.BuildFeedCardDto;
import group.goforward.battlbuilder.web.dto.build.BuildSummaryDto;
import group.goforward.battlbuilder.web.dto.UpdateBuildRequestDto;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.UUID;
@CrossOrigin
@RestController
@RequestMapping("/api/v1/builds")
public class BuildV1Controller {
private final BuildService buildService;
public BuildV1Controller(BuildService buildService) {
this.buildService = buildService;
}
/**
* Public builds feed for /builds page.
* GET /api/v1/builds?limit=50
*/
@GetMapping
public ResponseEntity<List<BuildFeedCardDto>> listPublicBuilds(
@RequestParam(name = "limit", required = false, defaultValue = "50") Integer limit
) {
return ResponseEntity.ok(buildService.listPublicBuilds(limit == null ? 50 : limit));
}
/**
* Public build detail for /builds/{uuid}
* GET /api/v1/builds/{uuid}
*/
@GetMapping("/{uuid}")
public ResponseEntity<BuildDto> getPublicBuild(@PathVariable("uuid") UUID uuid) {
return ResponseEntity.ok(buildService.getPublicBuild(uuid));
}
/**
* Vault builds (authenticated user).
* GET /api/v1/builds/me?limit=100
*/
@GetMapping("/me")
public ResponseEntity<List<BuildSummaryDto>> listMyBuilds(
@RequestParam(name = "limit", required = false, defaultValue = "100") Integer limit
) {
return ResponseEntity.ok(buildService.listMyBuilds(limit == null ? 100 : limit));
}
/**
* Load a single build (Vault edit + Builder ?load=uuid).
* GET /api/v1/builds/me/{uuid}
*/
@GetMapping("/me/{uuid}")
public ResponseEntity<BuildDto> getMyBuild(@PathVariable("uuid") UUID uuid) {
return ResponseEntity.ok(buildService.getMyBuild(uuid));
}
/**
* Create a NEW build in Vault (Save As…).
* POST /api/v1/builds/me
*/
@PostMapping("/me")
public ResponseEntity<BuildDto> createMyBuild(@RequestBody UpdateBuildRequestDto req) {
return ResponseEntity.ok(buildService.createMyBuild(req));
}
/**
* Update build (authenticated user; must own build eventually).
* PUT /api/v1/builds/me/{uuid}
*/
@PutMapping("/me/{uuid}")
public ResponseEntity<BuildDto> updateMyBuild(
@PathVariable("uuid") UUID uuid,
@RequestBody UpdateBuildRequestDto req
) {
return ResponseEntity.ok(buildService.updateMyBuild(uuid, req));
}
/**
* Delete a build (authenticated user; must own build).
* DELETE /api/v1/builds/me/{uuid}
*/
@DeleteMapping("/me/{uuid}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteMyBuild(@PathVariable("uuid") UUID uuid) {
buildService.deleteMyBuild(uuid);
}
}

View File

@@ -2,7 +2,7 @@ package group.goforward.battlbuilder.controllers.api.v1;
//
import group.goforward.battlbuilder.services.PartRoleMappingService;
import group.goforward.battlbuilder.web.dto.admin.PartRoleMappingDto;
import group.goforward.battlbuilder.web.dto.PartRoleToCategoryDto;
import group.goforward.battlbuilder.web.dto.mapping.PartRoleToCategoryDto;
import org.springframework.web.bind.annotation.*;
import java.util.List;

View File

@@ -1,54 +1,59 @@
package group.goforward.battlbuilder.controllers.api.v1;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* LEGACY CONTROLLER (Deprecated)
*
* Do not add new features here.
* Canonical API lives in ProductV1Controller (/api/v1/products).
*
* This exists only to keep older clients working temporarily.
* Disable by default using:
* app.api.legacy.enabled=false
*
* NOTE:
* Even when disabled, Spring still compiles this class. So it must not reference
* missing services/methods.
*/
@Deprecated
@RestController
@RequestMapping({"/api/products", "/api/v1/products"})
@CrossOrigin
@ConditionalOnProperty(name = "app.api.legacy.enabled", havingValue = "true", matchIfMissing = false)
public class ProductController {
private static final String MSG =
"Legacy endpoint disabled. Use /api/v1/products instead.";
@GetMapping
public ResponseEntity<?> getProducts(
@RequestParam(defaultValue = "AR-15") String platform,
@RequestParam(required = false, name = "partRoles") List<String> partRoles
) {
// Legacy disabled by design (Option B cleanup)
return ResponseEntity.status(410).body(MSG);
}
@GetMapping("/{id}/offers")
public ResponseEntity<?> getOffersForProduct(@PathVariable("id") Integer productId) {
return ResponseEntity.status(410).body(MSG);
}
@GetMapping("/{id}")
public ResponseEntity<?> getProductById(@PathVariable("id") Integer productId) {
return ResponseEntity.status(410).body(MSG);
}
// If you *really* need typed responses for an old client, we can re-add
// a real service layer once we align on the actual ProductQueryService API.
package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.services.ProductQueryService;
import group.goforward.battlbuilder.web.dto.product.ProductOfferDto;
import group.goforward.battlbuilder.web.dto.product.ProductSummaryDto;
import group.goforward.battlbuilder.web.dto.catalog.ProductSort;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/products")
@CrossOrigin
public class ProductController {
private final ProductQueryService productQueryService;
public ProductController(ProductQueryService productQueryService) {
this.productQueryService = productQueryService;
}
/**
* Product list endpoint
* Example:
* /api/v1/products?platform=AR-15&partRoles=upper-receiver&priceSort=price_asc&page=0&size=50
*
* NOTE: do NOT use `sort=` here — Spring reserves it for Pageable sorting.
*/
@GetMapping
@Cacheable(
value = "gunbuilderProductsV1",
key = "#platform + '::' + (#partRoles == null ? 'ALL' : #partRoles) + '::' + #priceSort + '::' + #pageable.pageNumber + '::' + #pageable.pageSize"
)
public Page<ProductSummaryDto> getProducts(
@RequestParam(defaultValue = "AR-15") String platform,
@RequestParam(required = false, name = "partRoles") List<String> partRoles,
@RequestParam(name = "priceSort", defaultValue = "price_asc") String priceSort,
@PageableDefault(size = 50) Pageable pageable
) {
ProductSort sortEnum = ProductSort.from(priceSort);
return productQueryService.getProductsPage(platform, partRoles, pageable, sortEnum);
}
@GetMapping("/{id}/offers")
public List<ProductOfferDto> getOffersForProduct(@PathVariable("id") Integer productId) {
return productQueryService.getOffersForProduct(productId);
}
@GetMapping("/{id}")
public ResponseEntity<ProductSummaryDto> getProductById(@PathVariable("id") Integer productId) {
ProductSummaryDto dto = productQueryService.getProductById(productId);
return dto != null ? ResponseEntity.ok(dto) : ResponseEntity.notFound().build();
}
}

View File

@@ -1,59 +0,0 @@
package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.services.ProductQueryService;
import group.goforward.battlbuilder.web.dto.product.ProductOfferDto;
import group.goforward.battlbuilder.web.dto.product.ProductSummaryDto;
import group.goforward.battlbuilder.web.dto.catalog.ProductSort;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/products")
@CrossOrigin
public class ProductV1Controller {
private final ProductQueryService productQueryService;
public ProductV1Controller(ProductQueryService productQueryService) {
this.productQueryService = productQueryService;
}
/**
* Product list endpoint
* Example:
* /api/v1/products?platform=AR-15&partRoles=upper-receiver&priceSort=price_asc&page=0&size=50
*
* NOTE: do NOT use `sort=` here — Spring reserves it for Pageable sorting.
*/
@GetMapping
@Cacheable(
value = "gunbuilderProductsV1",
key = "#platform + '::' + (#partRoles == null ? 'ALL' : #partRoles) + '::' + #priceSort + '::' + #pageable.pageNumber + '::' + #pageable.pageSize"
)
public Page<ProductSummaryDto> getProducts(
@RequestParam(defaultValue = "AR-15") String platform,
@RequestParam(required = false, name = "partRoles") List<String> partRoles,
@RequestParam(name = "priceSort", defaultValue = "price_asc") String priceSort,
@PageableDefault(size = 50) Pageable pageable
) {
ProductSort sortEnum = ProductSort.from(priceSort);
return productQueryService.getProductsPage(platform, partRoles, pageable, sortEnum);
}
@GetMapping("/{id}/offers")
public List<ProductOfferDto> getOffersForProduct(@PathVariable("id") Integer productId) {
return productQueryService.getOffersForProduct(productId);
}
@GetMapping("/{id}")
public ResponseEntity<ProductSummaryDto> getProductById(@PathVariable("id") Integer productId) {
ProductSummaryDto dto = productQueryService.getProductById(productId);
return dto != null ? ResponseEntity.ok(dto) : ResponseEntity.notFound().build();
}
}

View File

@@ -1,6 +1,6 @@
package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.web.dto.StateDto;
import group.goforward.battlbuilder.dto.StateDto;
import group.goforward.battlbuilder.mapper.StateMapper;
import group.goforward.battlbuilder.model.State;
import group.goforward.battlbuilder.repos.StateRepository;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.dto;
public class StateDto {

View File

@@ -3,7 +3,7 @@ package group.goforward.battlbuilder.mapper;
import group.goforward.battlbuilder.model.PartCategory;
import group.goforward.battlbuilder.model.PartRoleMapping;
import group.goforward.battlbuilder.web.dto.admin.PartRoleMappingDto;
import group.goforward.battlbuilder.web.dto.PartRoleToCategoryDto;
import group.goforward.battlbuilder.web.dto.mapping.PartRoleToCategoryDto;
public final class PartRoleMappingMapper {

View File

@@ -1,6 +1,6 @@
package group.goforward.battlbuilder.mapper;
import group.goforward.battlbuilder.web.dto.StateDto;
import group.goforward.battlbuilder.dto.StateDto;
import group.goforward.battlbuilder.model.State;

View File

@@ -3,7 +3,7 @@ package group.goforward.battlbuilder.services;
import group.goforward.battlbuilder.web.dto.build.BuildDto;
import group.goforward.battlbuilder.web.dto.build.BuildFeedCardDto;
import group.goforward.battlbuilder.web.dto.build.BuildSummaryDto;
import group.goforward.battlbuilder.web.dto.UpdateBuildRequestDto;
import group.goforward.battlbuilder.web.dto.mapping.UpdateBuildRequestDto;
import java.util.List;
import java.util.UUID;

View File

@@ -3,7 +3,7 @@ package group.goforward.battlbuilder.services;
import group.goforward.battlbuilder.model.Product;
import group.goforward.battlbuilder.repos.ProductRepository;
import group.goforward.battlbuilder.web.dto.CategoryMappingRecommendationDto;
import group.goforward.battlbuilder.web.dto.mapping.CategoryMappingRecommendationDto;
import org.springframework.stereotype.Service;
import java.util.List;

View File

@@ -8,9 +8,9 @@ import group.goforward.battlbuilder.repos.CanonicalCategoryRepository;
import group.goforward.battlbuilder.repos.MerchantCategoryMapRepository;
import group.goforward.battlbuilder.repos.MerchantRepository;
import group.goforward.battlbuilder.repos.ProductRepository;
import group.goforward.battlbuilder.web.dto.MappingOptionsDto;
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
import group.goforward.battlbuilder.web.dto.RawCategoryMappingRowDto;
import group.goforward.battlbuilder.web.dto.mapping.MappingOptionsDto;
import group.goforward.battlbuilder.web.dto.mapping.PendingMappingBucketDto;
import group.goforward.battlbuilder.web.dto.mapping.RawCategoryMappingRowDto;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

View File

@@ -2,7 +2,7 @@ package group.goforward.battlbuilder.services;
import group.goforward.battlbuilder.repos.PartRoleMappingRepository;
import group.goforward.battlbuilder.web.dto.admin.PartRoleMappingDto;
import group.goforward.battlbuilder.web.dto.PartRoleToCategoryDto;
import group.goforward.battlbuilder.web.dto.mapping.PartRoleToCategoryDto;
import group.goforward.battlbuilder.mapper.PartRoleMappingMapper;
import org.springframework.stereotype.Service;

View File

@@ -14,7 +14,7 @@ import group.goforward.battlbuilder.web.dto.build.BuildDto;
import group.goforward.battlbuilder.web.dto.build.BuildFeedCardDto;
import group.goforward.battlbuilder.web.dto.build.BuildItemDto;
import group.goforward.battlbuilder.web.dto.build.BuildSummaryDto;
import group.goforward.battlbuilder.web.dto.UpdateBuildRequestDto;
import group.goforward.battlbuilder.web.dto.mapping.UpdateBuildRequestDto;
import group.goforward.battlbuilder.repos.ProductRepository;
import org.springframework.data.domain.PageRequest;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.model.Caliber;
import group.goforward.battlbuilder.repos.CaliberRepository;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.model.enums.ImportStatus;
import group.goforward.battlbuilder.repos.ProductRepository;

View File

@@ -1,9 +1,9 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.services.MappingAdminService;
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
import group.goforward.battlbuilder.web.dto.MappingOptionsDto;
import group.goforward.battlbuilder.web.dto.RawCategoryMappingRowDto;
import group.goforward.battlbuilder.web.dto.mapping.PendingMappingBucketDto;
import group.goforward.battlbuilder.web.dto.mapping.MappingOptionsDto;
import group.goforward.battlbuilder.web.dto.mapping.RawCategoryMappingRowDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.services.MerchantFeedImportService;
import org.springframework.http.ResponseEntity;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.services.admin.AdminProductService;
import group.goforward.battlbuilder.web.dto.admin.AdminProductSearchRequest;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.common.Constants;
import group.goforward.battlbuilder.services.admin.AdminUserService;

View File

@@ -1,6 +1,6 @@
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;
import group.goforward.battlbuilder.web.dto.PendingMappingBucketDto;
import group.goforward.battlbuilder.web.dto.mapping.PendingMappingBucketDto;
import group.goforward.battlbuilder.services.MappingAdminService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

View File

@@ -9,4 +9,4 @@
* @version 1.0
* @since 2025-12-10
*/
package group.goforward.battlbuilder.web.admin;
package group.goforward.battlbuilder.web.admin.controller;

View File

@@ -1,5 +1,5 @@
// src/main/java/group/goforward/ballistic/web/dto/CategoryMappingRecommendationDto.java
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
public record CategoryMappingRecommendationDto(
String merchantName,

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
public record PartRoleToCategoryDto(
String platform,

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
public record PendingMappingBucketDto(
Integer merchantId,

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
public record RawCategoryMappingRowDto(
Integer merchantId,

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package group.goforward.battlbuilder.web.dto;
package group.goforward.battlbuilder.web.dto.mapping;
public class UpsertMerchantCategoryMappingRequest {