lots of changes that I don't think I made, must of been the pull

This commit is contained in:
2025-12-03 11:22:50 -05:00
parent 85b00e9d99
commit 4138edf45d
52 changed files with 2487 additions and 2467 deletions

View File

@@ -1,16 +1,16 @@
package group.goforward.ballistic.configuration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// Simple in-memory cache for dev/local
return new ConcurrentMapCacheManager("gunbuilderProducts");
}
package group.goforward.ballistic.configuration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// Simple in-memory cache for dev/local
return new ConcurrentMapCacheManager("gunbuilderProducts");
}
}

View File

@@ -1,13 +1,13 @@
/**
* Provides the classes necessary for the Spring Configurations for the ballistic -Builder application.
* This package includes Configurations for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Don Strawsburg
* @version 1.1
*/
/**
* Provides the classes necessary for the Spring Configurations for the ballistic -Builder application.
* This package includes Configurations for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Don Strawsburg
* @version 1.1
*/
package group.goforward.ballistic.configuration;

View File

@@ -1,17 +1,17 @@
# Stage 1: Build the application
FROM openjdk:17-jdk-slim as build
WORKDIR /app
COPY gradlew .
COPY settings.gradle .
COPY build.gradle .
COPY src ./src
# Adjust the build command for Maven: ./mvnw package -DskipTests
RUN ./gradlew bootJar
# Stage 2: Create the final lightweight image
FROM openjdk:17-jre-slim
WORKDIR /app
# Get the built JAR from the build stage
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080
# Stage 1: Build the application
FROM openjdk:17-jdk-slim as build
WORKDIR /app
COPY gradlew .
COPY settings.gradle .
COPY build.gradle .
COPY src ./src
# Adjust the build command for Maven: ./mvnw package -DskipTests
RUN ./gradlew bootJar
# Stage 2: Create the final lightweight image
FROM openjdk:17-jre-slim
WORKDIR /app
# Get the built JAR from the build stage
COPY --from=build /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@@ -1,39 +1,39 @@
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.services.MerchantFeedImportService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/imports")
@CrossOrigin(origins = "http://localhost:3000")
public class ImportController {
private final MerchantFeedImportService merchantFeedImportService;
public ImportController(MerchantFeedImportService merchantFeedImportService) {
this.merchantFeedImportService = merchantFeedImportService;
}
/**
* Full product + offer import for a merchant.
*
* POST /admin/imports/{merchantId}
*/
@PostMapping("/{merchantId}")
public ResponseEntity<Void> importMerchant(@PathVariable Integer merchantId) {
merchantFeedImportService.importMerchantFeed(merchantId);
return ResponseEntity.noContent().build();
}
/**
* Offers-only sync (price/stock) for a merchant.
*
* POST /admin/imports/{merchantId}/offers-only
*/
@PostMapping("/{merchantId}/offers-only")
public ResponseEntity<Void> syncOffersOnly(@PathVariable Integer merchantId) {
merchantFeedImportService.syncOffersOnly(merchantId);
return ResponseEntity.noContent().build();
}
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.services.MerchantFeedImportService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/imports")
@CrossOrigin(origins = "http://localhost:3000")
public class ImportController {
private final MerchantFeedImportService merchantFeedImportService;
public ImportController(MerchantFeedImportService merchantFeedImportService) {
this.merchantFeedImportService = merchantFeedImportService;
}
/**
* Full product + offer import for a merchant.
*
* POST /admin/imports/{merchantId}
*/
@PostMapping("/{merchantId}")
public ResponseEntity<Void> importMerchant(@PathVariable Integer merchantId) {
merchantFeedImportService.importMerchantFeed(merchantId);
return ResponseEntity.noContent().build();
}
/**
* Offers-only sync (price/stock) for a merchant.
*
* POST /admin/imports/{merchantId}/offers-only
*/
@PostMapping("/{merchantId}/offers-only")
public ResponseEntity<Void> syncOffersOnly(@PathVariable Integer merchantId) {
merchantFeedImportService.syncOffersOnly(merchantId);
return ResponseEntity.noContent().build();
}
}

View File

@@ -1,63 +1,63 @@
// MerchantAdminController.java
package group.goforward.ballistic.controllers;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.repos.MerchantRepository;
import group.goforward.ballistic.web.dto.MerchantAdminDto;
import org.springframework.web.bind.annotation.*;
import java.time.OffsetDateTime;
import java.util.List;
@RestController
@RequestMapping("/admin/merchants")
@CrossOrigin(origins = "http://localhost:3000") // TEMP for Cross-Origin Bug
public class MerchantAdminController {
private final MerchantRepository merchantRepository;
public MerchantAdminController(MerchantRepository merchantRepository) {
this.merchantRepository = merchantRepository;
}
@GetMapping
public List<MerchantAdminDto> listMerchants() {
return merchantRepository.findAll().stream().map(this::toDto).toList();
}
@PutMapping("/{id}")
public MerchantAdminDto updateMerchant(
@PathVariable Integer id,
@RequestBody MerchantAdminDto payload
) {
Merchant merchant = merchantRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Merchant not found"));
merchant.setFeedUrl(payload.getFeedUrl());
merchant.setOfferFeedUrl(payload.getOfferFeedUrl());
merchant.setIsActive(payload.getIsActive() != null ? payload.getIsActive() : true);
// dont touch last* here; those are set by import jobs
merchant = merchantRepository.save(merchant);
return toDto(merchant);
}
private MerchantAdminDto toDto(Merchant m) {
MerchantAdminDto dto = new MerchantAdminDto();
dto.setId(m.getId());
dto.setName(m.getName());
dto.setFeedUrl(m.getFeedUrl());
dto.setOfferFeedUrl(m.getOfferFeedUrl());
dto.setIsActive(m.getIsActive());
dto.setLastFullImportAt(m.getLastFullImportAt());
dto.setLastOfferSyncAt(m.getLastOfferSyncAt());
return dto;
}
// MerchantAdminController.java
package group.goforward.ballistic.controllers;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.repos.MerchantRepository;
import group.goforward.ballistic.web.dto.MerchantAdminDto;
import org.springframework.web.bind.annotation.*;
import java.time.OffsetDateTime;
import java.util.List;
@RestController
@RequestMapping("/admin/merchants")
@CrossOrigin(origins = "http://localhost:3000") // TEMP for Cross-Origin Bug
public class MerchantAdminController {
private final MerchantRepository merchantRepository;
public MerchantAdminController(MerchantRepository merchantRepository) {
this.merchantRepository = merchantRepository;
}
@GetMapping
public List<MerchantAdminDto> listMerchants() {
return merchantRepository.findAll().stream().map(this::toDto).toList();
}
@PutMapping("/{id}")
public MerchantAdminDto updateMerchant(
@PathVariable Integer id,
@RequestBody MerchantAdminDto payload
) {
Merchant merchant = merchantRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Merchant not found"));
merchant.setFeedUrl(payload.getFeedUrl());
merchant.setOfferFeedUrl(payload.getOfferFeedUrl());
merchant.setIsActive(payload.getIsActive() != null ? payload.getIsActive() : true);
// dont touch last* here; those are set by import jobs
merchant = merchantRepository.save(merchant);
return toDto(merchant);
}
private MerchantAdminDto toDto(Merchant m) {
MerchantAdminDto dto = new MerchantAdminDto();
dto.setId(m.getId());
dto.setName(m.getName());
dto.setFeedUrl(m.getFeedUrl());
dto.setOfferFeedUrl(m.getOfferFeedUrl());
dto.setIsActive(m.getIsActive());
dto.setLastFullImportAt(m.getLastFullImportAt());
dto.setLastOfferSyncAt(m.getLastOfferSyncAt());
return dto;
}
}

View File

@@ -1,65 +1,65 @@
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.model.MerchantCategoryMapping;
import group.goforward.ballistic.repos.MerchantRepository;
import group.goforward.ballistic.services.MerchantCategoryMappingService;
import group.goforward.ballistic.web.dto.MerchantCategoryMappingDto;
import group.goforward.ballistic.web.dto.UpsertMerchantCategoryMappingRequest;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/merchant-category-mappings")
@CrossOrigin
public class MerchantCategoryMappingController {
private final MerchantCategoryMappingService mappingService;
private final MerchantRepository merchantRepository;
public MerchantCategoryMappingController(
MerchantCategoryMappingService mappingService,
MerchantRepository merchantRepository
) {
this.mappingService = mappingService;
this.merchantRepository = merchantRepository;
}
@GetMapping
public List<MerchantCategoryMappingDto> listMappings(
@RequestParam("merchantId") Integer merchantId
) {
List<MerchantCategoryMapping> mappings = mappingService.findByMerchant(merchantId);
return mappings.stream()
.map(this::toDto)
.collect(Collectors.toList());
}
@PostMapping
public MerchantCategoryMappingDto upsertMapping(
@RequestBody UpsertMerchantCategoryMappingRequest request
) {
Merchant merchant = merchantRepository
.findById(request.getMerchantId())
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + request.getMerchantId()));
MerchantCategoryMapping mapping = mappingService.upsertMapping(
merchant,
request.getRawCategory(),
request.getMappedPartRole()
);
return toDto(mapping);
}
private MerchantCategoryMappingDto toDto(MerchantCategoryMapping mapping) {
MerchantCategoryMappingDto dto = new MerchantCategoryMappingDto();
dto.setId(mapping.getId());
dto.setMerchantId(mapping.getMerchant().getId());
dto.setMerchantName(mapping.getMerchant().getName());
dto.setRawCategory(mapping.getRawCategory());
dto.setMappedPartRole(mapping.getMappedPartRole());
return dto;
}
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.model.MerchantCategoryMapping;
import group.goforward.ballistic.repos.MerchantRepository;
import group.goforward.ballistic.services.MerchantCategoryMappingService;
import group.goforward.ballistic.web.dto.MerchantCategoryMappingDto;
import group.goforward.ballistic.web.dto.UpsertMerchantCategoryMappingRequest;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/merchant-category-mappings")
@CrossOrigin
public class MerchantCategoryMappingController {
private final MerchantCategoryMappingService mappingService;
private final MerchantRepository merchantRepository;
public MerchantCategoryMappingController(
MerchantCategoryMappingService mappingService,
MerchantRepository merchantRepository
) {
this.mappingService = mappingService;
this.merchantRepository = merchantRepository;
}
@GetMapping
public List<MerchantCategoryMappingDto> listMappings(
@RequestParam("merchantId") Integer merchantId
) {
List<MerchantCategoryMapping> mappings = mappingService.findByMerchant(merchantId);
return mappings.stream()
.map(this::toDto)
.collect(Collectors.toList());
}
@PostMapping
public MerchantCategoryMappingDto upsertMapping(
@RequestBody UpsertMerchantCategoryMappingRequest request
) {
Merchant merchant = merchantRepository
.findById(request.getMerchantId())
.orElseThrow(() -> new IllegalArgumentException("Merchant not found: " + request.getMerchantId()));
MerchantCategoryMapping mapping = mappingService.upsertMapping(
merchant,
request.getRawCategory(),
request.getMappedPartRole()
);
return toDto(mapping);
}
private MerchantCategoryMappingDto toDto(MerchantCategoryMapping mapping) {
MerchantCategoryMappingDto dto = new MerchantCategoryMappingDto();
dto.setId(mapping.getId());
dto.setMerchantId(mapping.getMerchant().getId());
dto.setMerchantName(mapping.getMerchant().getName());
dto.setRawCategory(mapping.getRawCategory());
dto.setMappedPartRole(mapping.getMappedPartRole());
return dto;
}
}

View File

@@ -1,23 +1,23 @@
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.repos.MerchantRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class MerchantDebugController {
private final MerchantRepository merchantRepository;
public MerchantDebugController(MerchantRepository merchantRepository) {
this.merchantRepository = merchantRepository;
}
@GetMapping("/admin/debug/merchants")
public List<Merchant> listMerchants() {
return merchantRepository.findAll();
}
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.repos.MerchantRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class MerchantDebugController {
private final MerchantRepository merchantRepository;
public MerchantDebugController(MerchantRepository merchantRepository) {
this.merchantRepository = merchantRepository;
}
@GetMapping("/admin/debug/merchants")
public List<Merchant> listMerchants() {
return merchantRepository.findAll();
}
}

View File

@@ -1,13 +1,13 @@
package group.goforward.ballistic.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PingController {
@GetMapping("/ping")
public String ping() {
return "pong";
}
package group.goforward.ballistic.controllers;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PingController {
@GetMapping("/ping")
public String ping() {
return "pong";
}
}

View File

@@ -1,137 +1,137 @@
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.model.ProductOffer;
import group.goforward.ballistic.repos.ProductOfferRepository;
import group.goforward.ballistic.web.dto.ProductOfferDto;
import group.goforward.ballistic.repos.ProductRepository;
import group.goforward.ballistic.web.dto.ProductSummaryDto;
import group.goforward.ballistic.web.mapper.ProductMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/products")
@CrossOrigin
public class ProductController {
private final ProductRepository productRepository;
private final ProductOfferRepository productOfferRepository;
public ProductController(
ProductRepository productRepository,
ProductOfferRepository productOfferRepository
) {
this.productRepository = productRepository;
this.productOfferRepository = productOfferRepository;
}
@GetMapping("/gunbuilder")
@Cacheable(
value = "gunbuilderProducts",
key = "#platform + '::' + (#partRoles == null ? 'ALL' : #partRoles.toString())"
)
public List<ProductSummaryDto> getGunbuilderProducts(
@RequestParam(defaultValue = "AR-15") String platform,
@RequestParam(required = false, name = "partRoles") List<String> partRoles
) {
long started = System.currentTimeMillis();
System.out.println("getGunbuilderProducts: start, platform=" + platform +
", partRoles=" + (partRoles == null ? "null" : partRoles));
// 1) Load products (with brand pre-fetched)
long tProductsStart = System.currentTimeMillis();
List<Product> products;
if (partRoles == null || partRoles.isEmpty()) {
products = productRepository.findByPlatformWithBrand(platform);
} else {
products = productRepository.findByPlatformAndPartRoleInWithBrand(platform, partRoles);
}
long tProductsEnd = System.currentTimeMillis();
System.out.println("getGunbuilderProducts: loaded products: " +
products.size() + " in " + (tProductsEnd - tProductsStart) + " ms");
if (products.isEmpty()) {
long took = System.currentTimeMillis() - started;
System.out.println("getGunbuilderProducts: 0 products in " + took + " ms");
return List.of();
}
// 2) Load offers for these product IDs
long tOffersStart = System.currentTimeMillis();
List<Integer> productIds = products.stream()
.map(Product::getId)
.toList();
List<ProductOffer> allOffers =
productOfferRepository.findByProductIdIn(productIds);
long tOffersEnd = System.currentTimeMillis();
System.out.println("getGunbuilderProducts: loaded offers: " +
allOffers.size() + " in " + (tOffersEnd - tOffersStart) + " ms");
Map<Integer, List<ProductOffer>> offersByProductId = allOffers.stream()
.collect(Collectors.groupingBy(o -> o.getProduct().getId()));
// 3) Map to DTOs with price and buyUrl
long tMapStart = System.currentTimeMillis();
List<ProductSummaryDto> result = products.stream()
.map(p -> {
List<ProductOffer> 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();
long tMapEnd = System.currentTimeMillis();
long took = System.currentTimeMillis() - started;
System.out.println("getGunbuilderProducts: mapping to DTOs took " +
(tMapEnd - tMapStart) + " ms");
System.out.println("getGunbuilderProducts: TOTAL " + took + " ms (" +
"products=" + (tProductsEnd - tProductsStart) + " ms, " +
"offers=" + (tOffersEnd - tOffersStart) + " ms, " +
"map=" + (tMapEnd - tMapStart) + " ms)");
return result;
}
@GetMapping("/{id}/offers")
public List<ProductOfferDto> getOffersForProduct(@PathVariable("id") Integer productId) {
List<ProductOffer> offers = productOfferRepository.findByProductId(productId);
return offers.stream()
.map(offer -> {
ProductOfferDto dto = new ProductOfferDto();
dto.setId(offer.getId().toString());
dto.setMerchantName(offer.getMerchant().getName());
dto.setPrice(offer.getEffectivePrice());
dto.setOriginalPrice(offer.getOriginalPrice());
dto.setInStock(Boolean.TRUE.equals(offer.getInStock()));
dto.setBuyUrl(offer.getBuyUrl());
dto.setLastUpdated(offer.getLastSeenAt());
return dto;
})
.toList();
}
private ProductOffer pickBestOffer(List<ProductOffer> offers) {
if (offers == null || offers.isEmpty()) {
return null;
}
// Right now: lowest price wins, regardless of stock (we set inStock=true on import anyway)
return offers.stream()
.filter(o -> o.getEffectivePrice() != null)
.min(Comparator.comparing(ProductOffer::getEffectivePrice))
.orElse(null);
}
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.model.ProductOffer;
import group.goforward.ballistic.repos.ProductOfferRepository;
import group.goforward.ballistic.web.dto.ProductOfferDto;
import group.goforward.ballistic.repos.ProductRepository;
import group.goforward.ballistic.web.dto.ProductSummaryDto;
import group.goforward.ballistic.web.mapper.ProductMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/products")
@CrossOrigin
public class ProductController {
private final ProductRepository productRepository;
private final ProductOfferRepository productOfferRepository;
public ProductController(
ProductRepository productRepository,
ProductOfferRepository productOfferRepository
) {
this.productRepository = productRepository;
this.productOfferRepository = productOfferRepository;
}
@GetMapping("/gunbuilder")
@Cacheable(
value = "gunbuilderProducts",
key = "#platform + '::' + (#partRoles == null ? 'ALL' : #partRoles.toString())"
)
public List<ProductSummaryDto> getGunbuilderProducts(
@RequestParam(defaultValue = "AR-15") String platform,
@RequestParam(required = false, name = "partRoles") List<String> partRoles
) {
long started = System.currentTimeMillis();
System.out.println("getGunbuilderProducts: start, platform=" + platform +
", partRoles=" + (partRoles == null ? "null" : partRoles));
// 1) Load products (with brand pre-fetched)
long tProductsStart = System.currentTimeMillis();
List<Product> products;
if (partRoles == null || partRoles.isEmpty()) {
products = productRepository.findByPlatformWithBrand(platform);
} else {
products = productRepository.findByPlatformAndPartRoleInWithBrand(platform, partRoles);
}
long tProductsEnd = System.currentTimeMillis();
System.out.println("getGunbuilderProducts: loaded products: " +
products.size() + " in " + (tProductsEnd - tProductsStart) + " ms");
if (products.isEmpty()) {
long took = System.currentTimeMillis() - started;
System.out.println("getGunbuilderProducts: 0 products in " + took + " ms");
return List.of();
}
// 2) Load offers for these product IDs
long tOffersStart = System.currentTimeMillis();
List<Integer> productIds = products.stream()
.map(Product::getId)
.toList();
List<ProductOffer> allOffers =
productOfferRepository.findByProductIdIn(productIds);
long tOffersEnd = System.currentTimeMillis();
System.out.println("getGunbuilderProducts: loaded offers: " +
allOffers.size() + " in " + (tOffersEnd - tOffersStart) + " ms");
Map<Integer, List<ProductOffer>> offersByProductId = allOffers.stream()
.collect(Collectors.groupingBy(o -> o.getProduct().getId()));
// 3) Map to DTOs with price and buyUrl
long tMapStart = System.currentTimeMillis();
List<ProductSummaryDto> result = products.stream()
.map(p -> {
List<ProductOffer> 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();
long tMapEnd = System.currentTimeMillis();
long took = System.currentTimeMillis() - started;
System.out.println("getGunbuilderProducts: mapping to DTOs took " +
(tMapEnd - tMapStart) + " ms");
System.out.println("getGunbuilderProducts: TOTAL " + took + " ms (" +
"products=" + (tProductsEnd - tProductsStart) + " ms, " +
"offers=" + (tOffersEnd - tOffersStart) + " ms, " +
"map=" + (tMapEnd - tMapStart) + " ms)");
return result;
}
@GetMapping("/{id}/offers")
public List<ProductOfferDto> getOffersForProduct(@PathVariable("id") Integer productId) {
List<ProductOffer> offers = productOfferRepository.findByProductId(productId);
return offers.stream()
.map(offer -> {
ProductOfferDto dto = new ProductOfferDto();
dto.setId(offer.getId().toString());
dto.setMerchantName(offer.getMerchant().getName());
dto.setPrice(offer.getEffectivePrice());
dto.setOriginalPrice(offer.getOriginalPrice());
dto.setInStock(Boolean.TRUE.equals(offer.getInStock()));
dto.setBuyUrl(offer.getBuyUrl());
dto.setLastUpdated(offer.getLastSeenAt());
return dto;
})
.toList();
}
private ProductOffer pickBestOffer(List<ProductOffer> offers) {
if (offers == null || offers.isEmpty()) {
return null;
}
// Right now: lowest price wins, regardless of stock (we set inStock=true on import anyway)
return offers.stream()
.filter(o -> o.getEffectivePrice() != null)
.min(Comparator.comparing(ProductOffer::getEffectivePrice))
.orElse(null);
}
}

View File

@@ -1,50 +1,53 @@
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.User;
import group.goforward.ballistic.repos.UserRepository;
import group.goforward.ballistic.services.UsersService;
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;
@RestController
@RequestMapping()
public class UserController {
@Autowired
private UserRepository repo;
@Autowired
private UsersService usersService;
@GetMapping("/api/getAllUsers")
public ResponseEntity<List<User>> getAllUsers() {
List<User> data = repo.findAll();
return ResponseEntity.ok(data);
}
@GetMapping("/api/getAllUsersById/{id}")
public ResponseEntity<User> getAllStatesById(@PathVariable Integer id) {
return repo.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/api/addUser")
public ResponseEntity<User> createUser(@RequestBody User item) {
User created = usersService.save(item);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@DeleteMapping("/api/deleteUser/{id}")
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
return usersService.findById(id)
.map(item -> {
usersService.deleteById(id);
return ResponseEntity.noContent().<Void>build();
})
.orElse(ResponseEntity.notFound().build());
}
}
package group.goforward.ballistic.controllers;
import group.goforward.ballistic.model.User;
import group.goforward.ballistic.repos.UserRepository;
import group.goforward.ballistic.services.UsersService;
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;
@RestController
@RequestMapping()
public class UserController {
private final UserRepository repo;
private final UsersService usersService;
public UserController(UserRepository repo, UsersService usersService) {
this.repo = repo;
this.usersService = usersService;
}
@GetMapping("/api/getAllUsers")
public ResponseEntity<List<User>> getAllUsers() {
List<User> data = repo.findAll();
return ResponseEntity.ok(data);
}
@GetMapping("/api/getAllUsersById/{id}")
public ResponseEntity<User> getAllStatesById(@PathVariable Integer id) {
return repo.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping("/api/addUser")
public ResponseEntity<User> createUser(@RequestBody User item) {
User created = usersService.save(item);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@DeleteMapping("/api/deleteUser/{id}")
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
return usersService.findById(id)
.map(item -> {
usersService.deleteById(id);
return ResponseEntity.noContent().<Void>build();
})
.orElse(ResponseEntity.notFound().build());
}
}

View File

@@ -1,13 +1,13 @@
/**
* Provides the classes necessary for the Spring Controllers for the ballistic -Builder application.
* This package includes Controllers for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Don Strawsburg
* @version 1.1
*/
/**
* Provides the classes necessary for the Spring Controllers for the ballistic -Builder application.
* This package includes Controllers for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Don Strawsburg
* @version 1.1
*/
package group.goforward.ballistic.controllers;

View File

@@ -1,30 +1,30 @@
package group.goforward.ballistic.imports;
import java.math.BigDecimal;
public record MerchantFeedRow(
String sku,
String manufacturerId,
String brandName,
String productName,
String longDescription,
String shortDescription,
String department,
String category,
String subCategory,
String thumbUrl,
String imageUrl,
String buyLink,
String keywords,
String reviews,
BigDecimal retailPrice,
BigDecimal salePrice,
String brandPageLink,
String brandLogoImage,
String productPageViewTracking,
String variantsXml,
String mediumImageUrl,
String productContentWidget,
String googleCategorization,
String itemBasedCommission
package group.goforward.ballistic.imports;
import java.math.BigDecimal;
public record MerchantFeedRow(
String sku,
String manufacturerId,
String brandName,
String productName,
String longDescription,
String shortDescription,
String department,
String category,
String subCategory,
String thumbUrl,
String imageUrl,
String buyLink,
String keywords,
String reviews,
BigDecimal retailPrice,
BigDecimal salePrice,
String brandPageLink,
String brandLogoImage,
String productPageViewTracking,
String variantsXml,
String mediumImageUrl,
String productContentWidget,
String googleCategorization,
String itemBasedCommission
) {}

View File

@@ -1,17 +1,17 @@
package group.goforward.ballistic.imports.dto;
import java.math.BigDecimal;
public record MerchantFeedRow(
String brandName,
String productName,
String mpn,
String upc,
String avantlinkProductId,
String sku,
String categoryPath,
String buyUrl,
BigDecimal price,
BigDecimal originalPrice,
boolean inStock
package group.goforward.ballistic.imports.dto;
import java.math.BigDecimal;
public record MerchantFeedRow(
String brandName,
String productName,
String mpn,
String upc,
String avantlinkProductId,
String sku,
String categoryPath,
String buyUrl,
BigDecimal price,
BigDecimal originalPrice,
boolean inStock
) {}

View File

@@ -1,13 +1,13 @@
/**
* Provides the classes necessary for the Spring Data Transfer Objects for the ballistic -Builder application.
* This package includes DTO for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Sean Strawsburg
* @version 1.1
*/
/**
* Provides the classes necessary for the Spring Data Transfer Objects for the ballistic -Builder application.
* This package includes DTO for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Sean Strawsburg
* @version 1.1
*/
package group.goforward.ballistic.imports.dto;

View File

@@ -1,105 +1,105 @@
package group.goforward.ballistic.model;
import jakarta.persistence.*;
import java.time.OffsetDateTime;
import group.goforward.ballistic.model.ProductConfiguration;
@Entity
@Table(
name = "merchant_category_mappings",
uniqueConstraints = @UniqueConstraint(
name = "uq_merchant_category",
columnNames = { "merchant_id", "raw_category" }
)
)
public class MerchantCategoryMapping {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // SERIAL
@Column(name = "id", nullable = false)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "merchant_id", nullable = false)
private Merchant merchant;
@Column(name = "raw_category", nullable = false, length = 512)
private String rawCategory;
@Column(name = "mapped_part_role", length = 128)
private String mappedPartRole; // e.g. "upper-receiver", "barrel"
@Column(name = "mapped_configuration")
@Enumerated(EnumType.STRING)
private ProductConfiguration mappedConfiguration;
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt = OffsetDateTime.now();
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt = OffsetDateTime.now();
@PreUpdate
public void onUpdate() {
this.updatedAt = OffsetDateTime.now();
}
// getters & setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Merchant getMerchant() {
return merchant;
}
public void setMerchant(Merchant merchant) {
this.merchant = merchant;
}
public String getRawCategory() {
return rawCategory;
}
public void setRawCategory(String rawCategory) {
this.rawCategory = rawCategory;
}
public String getMappedPartRole() {
return mappedPartRole;
}
public void setMappedPartRole(String mappedPartRole) {
this.mappedPartRole = mappedPartRole;
}
public ProductConfiguration getMappedConfiguration() {
return mappedConfiguration;
}
public void setMappedConfiguration(ProductConfiguration mappedConfiguration) {
this.mappedConfiguration = mappedConfiguration;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
package group.goforward.ballistic.model;
import jakarta.persistence.*;
import java.time.OffsetDateTime;
import group.goforward.ballistic.model.ProductConfiguration;
@Entity
@Table(
name = "merchant_category_mappings",
uniqueConstraints = @UniqueConstraint(
name = "uq_merchant_category",
columnNames = { "merchant_id", "raw_category" }
)
)
public class MerchantCategoryMapping {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // SERIAL
@Column(name = "id", nullable = false)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "merchant_id", nullable = false)
private Merchant merchant;
@Column(name = "raw_category", nullable = false, length = 512)
private String rawCategory;
@Column(name = "mapped_part_role", length = 128)
private String mappedPartRole; // e.g. "upper-receiver", "barrel"
@Column(name = "mapped_configuration")
@Enumerated(EnumType.STRING)
private ProductConfiguration mappedConfiguration;
@Column(name = "created_at", nullable = false)
private OffsetDateTime createdAt = OffsetDateTime.now();
@Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt = OffsetDateTime.now();
@PreUpdate
public void onUpdate() {
this.updatedAt = OffsetDateTime.now();
}
// getters & setters
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Merchant getMerchant() {
return merchant;
}
public void setMerchant(Merchant merchant) {
this.merchant = merchant;
}
public String getRawCategory() {
return rawCategory;
}
public void setRawCategory(String rawCategory) {
this.rawCategory = rawCategory;
}
public String getMappedPartRole() {
return mappedPartRole;
}
public void setMappedPartRole(String mappedPartRole) {
this.mappedPartRole = mappedPartRole;
}
public ProductConfiguration getMappedConfiguration() {
return mappedConfiguration;
}
public void setMappedConfiguration(ProductConfiguration mappedConfiguration) {
this.mappedConfiguration = mappedConfiguration;
}
public OffsetDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(OffsetDateTime createdAt) {
this.createdAt = createdAt;
}
public OffsetDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(OffsetDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@@ -1,10 +1,10 @@
package group.goforward.ballistic.model;
public enum ProductConfiguration {
STRIPPED, // bare receiver / component
ASSEMBLED, // built up but not fully complete
BARRELED, // upper + barrel + gas system, no BCG/CH
COMPLETE, // full assembly ready to run
KIT, // collection of parts (LPK, trigger kits, etc.)
OTHER // fallback / unknown
package group.goforward.ballistic.model;
public enum ProductConfiguration {
STRIPPED, // bare receiver / component
ASSEMBLED, // built up but not fully complete
BARRELED, // upper + barrel + gas system, no BCG/CH
COMPLETE, // full assembly ready to run
KIT, // collection of parts (LPK, trigger kits, etc.)
OTHER // fallback / unknown
}

View File

@@ -1,9 +1,9 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface AccountRepository extends JpaRepository<Account, UUID> {
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface AccountRepository extends JpaRepository<Account, UUID> {
}

View File

@@ -1,8 +1,8 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Brand;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface BrandRepository extends JpaRepository<Brand, Integer> {
Optional<Brand> findByNameIgnoreCase(String name);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Brand;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface BrandRepository extends JpaRepository<Brand, Integer> {
Optional<Brand> findByNameIgnoreCase(String name);
}

View File

@@ -1,12 +1,12 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.BuildsComponent;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface BuildItemRepository extends JpaRepository<BuildsComponent, Integer> {
List<BuildsComponent> findByBuildId(Integer buildId);
Optional<BuildsComponent> findByUuid(UUID uuid);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.BuildsComponent;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface BuildItemRepository extends JpaRepository<BuildsComponent, Integer> {
List<BuildsComponent> findByBuildId(Integer buildId);
Optional<BuildsComponent> findByUuid(UUID uuid);
}

View File

@@ -1,10 +1,10 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Build;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface BuildRepository extends JpaRepository<Build, Integer> {
Optional<Build> findByUuid(UUID uuid);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Build;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface BuildRepository extends JpaRepository<Build, Integer> {
Optional<Build> findByUuid(UUID uuid);
}

View File

@@ -1,7 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.AffiliateCategoryMap;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CategoryMappingRepository extends JpaRepository<AffiliateCategoryMap, Integer> {
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.AffiliateCategoryMap;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CategoryMappingRepository extends JpaRepository<AffiliateCategoryMap, Integer> {
}

View File

@@ -1,7 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.FeedImport;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FeedImportRepository extends JpaRepository<FeedImport, Long> {
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.FeedImport;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FeedImportRepository extends JpaRepository<FeedImport, Long> {
}

View File

@@ -1,17 +1,17 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.MerchantCategoryMapping;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MerchantCategoryMappingRepository
extends JpaRepository<MerchantCategoryMapping, Integer> {
Optional<MerchantCategoryMapping> findByMerchantIdAndRawCategoryIgnoreCase(
Integer merchantId,
String rawCategory
);
List<MerchantCategoryMapping> findByMerchantIdOrderByRawCategoryAsc(Integer merchantId);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.MerchantCategoryMapping;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MerchantCategoryMappingRepository
extends JpaRepository<MerchantCategoryMapping, Integer> {
Optional<MerchantCategoryMapping> findByMerchantIdAndRawCategoryIgnoreCase(
Integer merchantId,
String rawCategory
);
List<MerchantCategoryMapping> findByMerchantIdOrderByRawCategoryAsc(Integer merchantId);
}

View File

@@ -1,11 +1,11 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Merchant;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MerchantRepository extends JpaRepository<Merchant, Integer> {
Optional<Merchant> findByNameIgnoreCase(String name);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Merchant;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MerchantRepository extends JpaRepository<Merchant, Integer> {
Optional<Merchant> findByNameIgnoreCase(String name);
}

View File

@@ -1,9 +1,9 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.PartCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface PartCategoryRepository extends JpaRepository<PartCategory, Integer> {
Optional<PartCategory> findBySlug(String slug);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.PartCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface PartCategoryRepository extends JpaRepository<PartCategory, Integer> {
Optional<PartCategory> findBySlug(String slug);
}

View File

@@ -1,7 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.PriceHistory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PriceHistoryRepository extends JpaRepository<PriceHistory, Long> {
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.PriceHistory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PriceHistoryRepository extends JpaRepository<PriceHistory, Long> {
}

View File

@@ -1,22 +1,22 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.ProductOffer;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public interface ProductOfferRepository extends JpaRepository<ProductOffer, Integer> {
List<ProductOffer> findByProductId(Integer productId);
// Used by the /api/products/gunbuilder endpoint
List<ProductOffer> findByProductIdIn(Collection<Integer> productIds);
// Unique offer lookup for importer upsert
Optional<ProductOffer> findByMerchantIdAndAvantlinkProductId(
Integer merchantId,
String avantlinkProductId
);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.ProductOffer;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public interface ProductOfferRepository extends JpaRepository<ProductOffer, Integer> {
List<ProductOffer> findByProductId(Integer productId);
// Used by the /api/products/gunbuilder endpoint
List<ProductOffer> findByProductIdIn(Collection<Integer> productIds);
// Unique offer lookup for importer upsert
Optional<ProductOffer> findByMerchantIdAndAvantlinkProductId(
Integer merchantId,
String avantlinkProductId
);
}

View File

@@ -1,53 +1,53 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.model.Brand;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
import java.util.UUID;
import java.util.List;
import java.util.Collection;
public interface ProductRepository extends JpaRepository<Product, Integer> {
Optional<Product> findByUuid(UUID uuid);
boolean existsBySlug(String slug);
List<Product> findAllByBrandAndMpn(Brand brand, String mpn);
List<Product> findAllByBrandAndUpc(Brand brand, String upc);
// All products for a given platform (e.g. "AR-15")
List<Product> findByPlatform(String platform);
// Products filtered by platform + part roles (e.g. upper-receiver, barrel, etc.)
List<Product> findByPlatformAndPartRoleIn(String platform, Collection<String> partRoles);
// ---------- Optimized variants for Gunbuilder (fetch brand to avoid N+1) ----------
@Query("""
SELECT p
FROM Product p
JOIN FETCH p.brand b
WHERE p.platform = :platform
AND p.deletedAt IS NULL
""")
List<Product> findByPlatformWithBrand(@Param("platform") String platform);
@Query("""
SELECT p
FROM Product p
JOIN FETCH p.brand b
WHERE p.platform = :platform
AND p.partRole IN :partRoles
AND p.deletedAt IS NULL
""")
List<Product> findByPlatformAndPartRoleInWithBrand(
@Param("platform") String platform,
@Param("partRoles") Collection<String> partRoles
);
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.model.Brand;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
import java.util.UUID;
import java.util.List;
import java.util.Collection;
public interface ProductRepository extends JpaRepository<Product, Integer> {
Optional<Product> findByUuid(UUID uuid);
boolean existsBySlug(String slug);
List<Product> findAllByBrandAndMpn(Brand brand, String mpn);
List<Product> findAllByBrandAndUpc(Brand brand, String upc);
// All products for a given platform (e.g. "AR-15")
List<Product> findByPlatform(String platform);
// Products filtered by platform + part roles (e.g. upper-receiver, barrel, etc.)
List<Product> findByPlatformAndPartRoleIn(String platform, Collection<String> partRoles);
// ---------- Optimized variants for Gunbuilder (fetch brand to avoid N+1) ----------
@Query("""
SELECT p
FROM Product p
JOIN FETCH p.brand b
WHERE p.platform = :platform
AND p.deletedAt IS NULL
""")
List<Product> findByPlatformWithBrand(@Param("platform") String platform);
@Query("""
SELECT p
FROM Product p
JOIN FETCH p.brand b
WHERE p.platform = :platform
AND p.partRole IN :partRoles
AND p.deletedAt IS NULL
""")
List<Product> findByPlatformAndPartRoleInWithBrand(
@Param("platform") String platform,
@Param("partRoles") Collection<String> partRoles
);
}

View File

@@ -1,13 +1,13 @@
/**
* Provides the classes necessary for the Spring Repository for the ballistic -Builder application.
* This package includes Repository for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Sean Strawsburg
* @version 1.1
*/
/**
* Provides the classes necessary for the Spring Repository for the ballistic -Builder application.
* This package includes Repository for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Sean Strawsburg
* @version 1.1
*/
package group.goforward.ballistic.repos;

View File

@@ -1,96 +1,96 @@
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.model.MerchantCategoryMapping;
import group.goforward.ballistic.model.ProductConfiguration;
import group.goforward.ballistic.repos.MerchantCategoryMappingRepository;
import jakarta.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
@Service
public class MerchantCategoryMappingService {
private final MerchantCategoryMappingRepository mappingRepository;
public MerchantCategoryMappingService(MerchantCategoryMappingRepository mappingRepository) {
this.mappingRepository = mappingRepository;
}
public List<MerchantCategoryMapping> findByMerchant(Integer merchantId) {
return mappingRepository.findByMerchantIdOrderByRawCategoryAsc(merchantId);
}
/**
* Resolve (or create) a mapping row for this merchant + raw category.
* - If it exists, returns it (with whatever mappedPartRole / mappedConfiguration are set).
* - If it doesn't exist, creates a placeholder row with null mappings and returns it.
*
* The importer can then:
* - skip rows where mappedPartRole is still null
* - use mappedConfiguration if present
*/
@Transactional
public MerchantCategoryMapping resolveMapping(Merchant merchant, String rawCategory) {
if (rawCategory == null || rawCategory.isBlank()) {
return null;
}
String trimmed = rawCategory.trim();
return mappingRepository
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
.orElseGet(() -> {
MerchantCategoryMapping mapping = new MerchantCategoryMapping();
mapping.setMerchant(merchant);
mapping.setRawCategory(trimmed);
mapping.setMappedPartRole(null);
mapping.setMappedConfiguration(null);
return mappingRepository.save(mapping);
});
}
/**
* Upsert mapping (admin UI).
*/
@Transactional
public MerchantCategoryMapping upsertMapping(
Merchant merchant,
String rawCategory,
String mappedPartRole,
ProductConfiguration mappedConfiguration
) {
String trimmed = rawCategory.trim();
MerchantCategoryMapping mapping = mappingRepository
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
.orElseGet(() -> {
MerchantCategoryMapping m = new MerchantCategoryMapping();
m.setMerchant(merchant);
m.setRawCategory(trimmed);
return m;
});
mapping.setMappedPartRole(
(mappedPartRole == null || mappedPartRole.isBlank()) ? null : mappedPartRole.trim()
);
mapping.setMappedConfiguration(mappedConfiguration);
return mappingRepository.save(mapping);
}
/**
* Backwards-compatible overload for existing callers (e.g. controller)
* that dont care about productConfiguration yet.
*/
@Transactional
public MerchantCategoryMapping upsertMapping(
Merchant merchant,
String rawCategory,
String mappedPartRole
) {
// Delegate to the new method with `null` configuration
return upsertMapping(merchant, rawCategory, mappedPartRole, null);
}
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.model.MerchantCategoryMapping;
import group.goforward.ballistic.model.ProductConfiguration;
import group.goforward.ballistic.repos.MerchantCategoryMappingRepository;
import jakarta.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
@Service
public class MerchantCategoryMappingService {
private final MerchantCategoryMappingRepository mappingRepository;
public MerchantCategoryMappingService(MerchantCategoryMappingRepository mappingRepository) {
this.mappingRepository = mappingRepository;
}
public List<MerchantCategoryMapping> findByMerchant(Integer merchantId) {
return mappingRepository.findByMerchantIdOrderByRawCategoryAsc(merchantId);
}
/**
* Resolve (or create) a mapping row for this merchant + raw category.
* - If it exists, returns it (with whatever mappedPartRole / mappedConfiguration are set).
* - If it doesn't exist, creates a placeholder row with null mappings and returns it.
*
* The importer can then:
* - skip rows where mappedPartRole is still null
* - use mappedConfiguration if present
*/
@Transactional
public MerchantCategoryMapping resolveMapping(Merchant merchant, String rawCategory) {
if (rawCategory == null || rawCategory.isBlank()) {
return null;
}
String trimmed = rawCategory.trim();
return mappingRepository
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
.orElseGet(() -> {
MerchantCategoryMapping mapping = new MerchantCategoryMapping();
mapping.setMerchant(merchant);
mapping.setRawCategory(trimmed);
mapping.setMappedPartRole(null);
mapping.setMappedConfiguration(null);
return mappingRepository.save(mapping);
});
}
/**
* Upsert mapping (admin UI).
*/
@Transactional
public MerchantCategoryMapping upsertMapping(
Merchant merchant,
String rawCategory,
String mappedPartRole,
ProductConfiguration mappedConfiguration
) {
String trimmed = rawCategory.trim();
MerchantCategoryMapping mapping = mappingRepository
.findByMerchantIdAndRawCategoryIgnoreCase(merchant.getId(), trimmed)
.orElseGet(() -> {
MerchantCategoryMapping m = new MerchantCategoryMapping();
m.setMerchant(merchant);
m.setRawCategory(trimmed);
return m;
});
mapping.setMappedPartRole(
(mappedPartRole == null || mappedPartRole.isBlank()) ? null : mappedPartRole.trim()
);
mapping.setMappedConfiguration(mappedConfiguration);
return mappingRepository.save(mapping);
}
/**
* Backwards-compatible overload for existing callers (e.g. controller)
* that dont care about productConfiguration yet.
*/
@Transactional
public MerchantCategoryMapping upsertMapping(
Merchant merchant,
String rawCategory,
String mappedPartRole
) {
// Delegate to the new method with `null` configuration
return upsertMapping(merchant, rawCategory, mappedPartRole, null);
}
}

View File

@@ -1,14 +1,14 @@
package group.goforward.ballistic.services;
public interface MerchantFeedImportService {
/**
* Full product + offer import for a given merchant.
*/
void importMerchantFeed(Integer merchantId);
/**
* Offers-only sync (price / stock) for a given merchant.
*/
void syncOffersOnly(Integer merchantId);
package group.goforward.ballistic.services;
public interface MerchantFeedImportService {
/**
* Full product + offer import for a given merchant.
*/
void importMerchantFeed(Integer merchantId);
/**
* Offers-only sync (price / stock) for a given merchant.
*/
void syncOffersOnly(Integer merchantId);
}

View File

@@ -1,17 +1,17 @@
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.Psa;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface PsaService {
List<Psa> findAll();
Optional<Psa> findById(UUID id);
Psa save(Psa psa);
void deleteById(UUID id);
}
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.Psa;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface PsaService {
List<Psa> findAll();
Optional<Psa> findById(UUID id);
Psa save(Psa psa);
void deleteById(UUID id);
}

View File

@@ -1,16 +1,16 @@
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.State;
import java.util.List;
import java.util.Optional;
public interface StatesService {
List<State> findAll();
Optional<State> findById(Integer id);
State save(State item);
void deleteById(Integer id);
}
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.State;
import java.util.List;
import java.util.Optional;
public interface StatesService {
List<State> findAll();
Optional<State> findById(Integer id);
State save(State item);
void deleteById(Integer id);
}

View File

@@ -1,16 +1,16 @@
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.User;
import java.util.List;
import java.util.Optional;
public interface UsersService {
List<User> findAll();
Optional<User> findById(Integer id);
User save(User item);
void deleteById(Integer id);
}
package group.goforward.ballistic.services;
import group.goforward.ballistic.model.User;
import java.util.List;
import java.util.Optional;
public interface UsersService {
List<User> findAll();
Optional<User> findById(Integer id);
User save(User item);
void deleteById(Integer id);
}

View File

@@ -1,41 +1,41 @@
package group.goforward.ballistic.services.impl;
import group.goforward.ballistic.model.Psa;
import group.goforward.ballistic.repos.PsaRepository;
import group.goforward.ballistic.services.PsaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
public class PsaServiceImpl implements PsaService {
private final PsaRepository psaRepository;
@Autowired
public PsaServiceImpl(PsaRepository psaRepository) {
this.psaRepository = psaRepository;
}
@Override
public List<Psa> findAll() {
return psaRepository.findAll();
}
@Override
public Optional<Psa> findById(UUID id) {
return psaRepository.findById(id);
}
@Override
public Psa save(Psa psa) {
return psaRepository.save(psa);
}
@Override
public void deleteById(UUID id) {
psaRepository.deleteById(id);
}
}
package group.goforward.ballistic.services.impl;
import group.goforward.ballistic.model.Psa;
import group.goforward.ballistic.repos.PsaRepository;
import group.goforward.ballistic.services.PsaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
public class PsaServiceImpl implements PsaService {
private final PsaRepository psaRepository;
@Autowired
public PsaServiceImpl(PsaRepository psaRepository) {
this.psaRepository = psaRepository;
}
@Override
public List<Psa> findAll() {
return psaRepository.findAll();
}
@Override
public Optional<Psa> findById(UUID id) {
return psaRepository.findById(id);
}
@Override
public Psa save(Psa psa) {
return psaRepository.save(psa);
}
@Override
public void deleteById(UUID id) {
psaRepository.deleteById(id);
}
}

View File

@@ -1,38 +1,38 @@
package group.goforward.ballistic.services.impl;
import group.goforward.ballistic.model.State;
import group.goforward.ballistic.repos.StateRepository;
import group.goforward.ballistic.services.StatesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class StatesServiceImpl implements StatesService {
@Autowired
private StateRepository repo;
@Override
public List<State> findAll() {
return repo.findAll();
}
@Override
public Optional<State> findById(Integer id) {
return repo.findById(id);
}
@Override
public State save(State item) {
return null;
}
@Override
public void deleteById(Integer id) {
deleteById(id);
}
}
package group.goforward.ballistic.services.impl;
import group.goforward.ballistic.model.State;
import group.goforward.ballistic.repos.StateRepository;
import group.goforward.ballistic.services.StatesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class StatesServiceImpl implements StatesService {
@Autowired
private StateRepository repo;
@Override
public List<State> findAll() {
return repo.findAll();
}
@Override
public Optional<State> findById(Integer id) {
return repo.findById(id);
}
@Override
public State save(State item) {
return null;
}
@Override
public void deleteById(Integer id) {
deleteById(id);
}
}

View File

@@ -1,37 +1,37 @@
package group.goforward.ballistic.services.impl;
import group.goforward.ballistic.model.User;
import group.goforward.ballistic.repos.UserRepository;
import group.goforward.ballistic.services.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UsersServiceImpl implements UsersService {
@Autowired
private UserRepository repo;
@Override
public List<User> findAll() {
return repo.findAll();
}
@Override
public Optional<User> findById(Integer id) {
return repo.findById(id);
}
@Override
public User save(User item) {
return null;
}
@Override
public void deleteById(Integer id) {
deleteById(id);
}
}
package group.goforward.ballistic.services.impl;
import group.goforward.ballistic.model.User;
import group.goforward.ballistic.repos.UserRepository;
import group.goforward.ballistic.services.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UsersServiceImpl implements UsersService {
@Autowired
private UserRepository repo;
@Override
public List<User> findAll() {
return repo.findAll();
}
@Override
public Optional<User> findById(Integer id) {
return repo.findById(id);
}
@Override
public User save(User item) {
return null;
}
@Override
public void deleteById(Integer id) {
deleteById(id);
}
}

View File

@@ -1,13 +1,13 @@
/**
* Provides the classes necessary for the Spring Services implementations for the ballistic -Builder application.
* This package includes Services implementations for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Don Strawsburg
* @version 1.1
*/
/**
* Provides the classes necessary for the Spring Services implementations for the ballistic -Builder application.
* This package includes Services implementations for Spring-Boot application
*
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.ballistic.BallisticApplication} class.</p>
*
* @since 1.0
* @author Don Strawsburg
* @version 1.1
*/
package group.goforward.ballistic.services.impl;

View File

@@ -1,70 +1,70 @@
// MerchantAdminDto.java
package group.goforward.ballistic.web.dto;
import java.time.OffsetDateTime;
public class MerchantAdminDto {
private Integer id;
private String name;
private String feedUrl;
private String offerFeedUrl;
private Boolean isActive;
private OffsetDateTime lastFullImportAt;
private OffsetDateTime lastOfferSyncAt;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFeedUrl() {
return feedUrl;
}
public void setFeedUrl(String feedUrl) {
this.feedUrl = feedUrl;
}
public String getOfferFeedUrl() {
return offerFeedUrl;
}
public void setOfferFeedUrl(String offerFeedUrl) {
this.offerFeedUrl = offerFeedUrl;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public OffsetDateTime getLastFullImportAt() {
return lastFullImportAt;
}
public void setLastFullImportAt(OffsetDateTime lastFullImportAt) {
this.lastFullImportAt = lastFullImportAt;
}
public OffsetDateTime getLastOfferSyncAt() {
return lastOfferSyncAt;
}
public void setLastOfferSyncAt(OffsetDateTime lastOfferSyncAt) {
this.lastOfferSyncAt = lastOfferSyncAt;
}
// MerchantAdminDto.java
package group.goforward.ballistic.web.dto;
import java.time.OffsetDateTime;
public class MerchantAdminDto {
private Integer id;
private String name;
private String feedUrl;
private String offerFeedUrl;
private Boolean isActive;
private OffsetDateTime lastFullImportAt;
private OffsetDateTime lastOfferSyncAt;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFeedUrl() {
return feedUrl;
}
public void setFeedUrl(String feedUrl) {
this.feedUrl = feedUrl;
}
public String getOfferFeedUrl() {
return offerFeedUrl;
}
public void setOfferFeedUrl(String offerFeedUrl) {
this.offerFeedUrl = offerFeedUrl;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public OffsetDateTime getLastFullImportAt() {
return lastFullImportAt;
}
public void setLastFullImportAt(OffsetDateTime lastFullImportAt) {
this.lastFullImportAt = lastFullImportAt;
}
public OffsetDateTime getLastOfferSyncAt() {
return lastOfferSyncAt;
}
public void setLastOfferSyncAt(OffsetDateTime lastOfferSyncAt) {
this.lastOfferSyncAt = lastOfferSyncAt;
}
}

View File

@@ -1,50 +1,50 @@
package group.goforward.ballistic.web.dto;
public class MerchantCategoryMappingDto {
private Integer id;
private Integer merchantId;
private String merchantName;
private String rawCategory;
private String mappedPartRole;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getMerchantId() {
return merchantId;
}
public void setMerchantId(Integer merchantId) {
this.merchantId = merchantId;
}
public String getMerchantName() {
return merchantName;
}
public void setMerchantName(String merchantName) {
this.merchantName = merchantName;
}
public String getRawCategory() {
return rawCategory;
}
public void setRawCategory(String rawCategory) {
this.rawCategory = rawCategory;
}
public String getMappedPartRole() {
return mappedPartRole;
}
public void setMappedPartRole(String mappedPartRole) {
this.mappedPartRole = mappedPartRole;
}
package group.goforward.ballistic.web.dto;
public class MerchantCategoryMappingDto {
private Integer id;
private Integer merchantId;
private String merchantName;
private String rawCategory;
private String mappedPartRole;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getMerchantId() {
return merchantId;
}
public void setMerchantId(Integer merchantId) {
this.merchantId = merchantId;
}
public String getMerchantName() {
return merchantName;
}
public void setMerchantName(String merchantName) {
this.merchantName = merchantName;
}
public String getRawCategory() {
return rawCategory;
}
public void setRawCategory(String rawCategory) {
this.rawCategory = rawCategory;
}
public String getMappedPartRole() {
return mappedPartRole;
}
public void setMappedPartRole(String mappedPartRole) {
this.mappedPartRole = mappedPartRole;
}
}

View File

@@ -1,70 +1,70 @@
package group.goforward.ballistic.web.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
public class ProductOfferDto {
private String id;
private String merchantName;
private BigDecimal price;
private BigDecimal originalPrice;
private boolean inStock;
private String buyUrl;
private OffsetDateTime lastUpdated;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMerchantName() {
return merchantName;
}
public void setMerchantName(String merchantName) {
this.merchantName = merchantName;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getOriginalPrice() {
return originalPrice;
}
public void setOriginalPrice(BigDecimal originalPrice) {
this.originalPrice = originalPrice;
}
public boolean isInStock() {
return inStock;
}
public void setInStock(boolean inStock) {
this.inStock = inStock;
}
public String getBuyUrl() {
return buyUrl;
}
public void setBuyUrl(String buyUrl) {
this.buyUrl = buyUrl;
}
public OffsetDateTime getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(OffsetDateTime lastUpdated) {
this.lastUpdated = lastUpdated;
}
package group.goforward.ballistic.web.dto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
public class ProductOfferDto {
private String id;
private String merchantName;
private BigDecimal price;
private BigDecimal originalPrice;
private boolean inStock;
private String buyUrl;
private OffsetDateTime lastUpdated;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMerchantName() {
return merchantName;
}
public void setMerchantName(String merchantName) {
this.merchantName = merchantName;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getOriginalPrice() {
return originalPrice;
}
public void setOriginalPrice(BigDecimal originalPrice) {
this.originalPrice = originalPrice;
}
public boolean isInStock() {
return inStock;
}
public void setInStock(boolean inStock) {
this.inStock = inStock;
}
public String getBuyUrl() {
return buyUrl;
}
public void setBuyUrl(String buyUrl) {
this.buyUrl = buyUrl;
}
public OffsetDateTime getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(OffsetDateTime lastUpdated) {
this.lastUpdated = lastUpdated;
}
}

View File

@@ -1,79 +1,79 @@
package group.goforward.ballistic.web.dto;
import java.math.BigDecimal;
public class ProductSummaryDto {
private String id; // product UUID as string
private String name;
private String brand;
private String platform;
private String partRole;
private String categoryKey;
private BigDecimal price;
private String buyUrl;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
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 getCategoryKey() {
return categoryKey;
}
public void setCategoryKey(String categoryKey) {
this.categoryKey = categoryKey;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getBuyUrl() {
return buyUrl;
}
public void setBuyUrl(String buyUrl) {
this.buyUrl = buyUrl;
}
package group.goforward.ballistic.web.dto;
import java.math.BigDecimal;
public class ProductSummaryDto {
private String id; // product UUID as string
private String name;
private String brand;
private String platform;
private String partRole;
private String categoryKey;
private BigDecimal price;
private String buyUrl;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
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 getCategoryKey() {
return categoryKey;
}
public void setCategoryKey(String categoryKey) {
this.categoryKey = categoryKey;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getBuyUrl() {
return buyUrl;
}
public void setBuyUrl(String buyUrl) {
this.buyUrl = buyUrl;
}
}

View File

@@ -1,32 +1,32 @@
package group.goforward.ballistic.web.dto;
public class UpsertMerchantCategoryMappingRequest {
private Integer merchantId;
private String rawCategory;
private String mappedPartRole; // can be null to "unmap"
public Integer getMerchantId() {
return merchantId;
}
public void setMerchantId(Integer merchantId) {
this.merchantId = merchantId;
}
public String getRawCategory() {
return rawCategory;
}
public void setRawCategory(String rawCategory) {
this.rawCategory = rawCategory;
}
public String getMappedPartRole() {
return mappedPartRole;
}
public void setMappedPartRole(String mappedPartRole) {
this.mappedPartRole = mappedPartRole;
}
package group.goforward.ballistic.web.dto;
public class UpsertMerchantCategoryMappingRequest {
private Integer merchantId;
private String rawCategory;
private String mappedPartRole; // can be null to "unmap"
public Integer getMerchantId() {
return merchantId;
}
public void setMerchantId(Integer merchantId) {
this.merchantId = merchantId;
}
public String getRawCategory() {
return rawCategory;
}
public void setRawCategory(String rawCategory) {
this.rawCategory = rawCategory;
}
public String getMappedPartRole() {
return mappedPartRole;
}
public void setMappedPartRole(String mappedPartRole) {
this.mappedPartRole = mappedPartRole;
}
}

View File

@@ -1,30 +1,30 @@
package group.goforward.ballistic.web.mapper;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.web.dto.ProductSummaryDto;
import java.math.BigDecimal;
public class ProductMapper {
public static ProductSummaryDto toSummary(Product product, BigDecimal price, String buyUrl) {
ProductSummaryDto dto = new ProductSummaryDto();
// Product ID -> String
dto.setId(String.valueOf(product.getId()));
dto.setName(product.getName());
dto.setBrand(product.getBrand() != null ? product.getBrand().getName() : null);
dto.setPlatform(product.getPlatform());
dto.setPartRole(product.getPartRole());
// Use rawCategoryKey from the Product entity
dto.setCategoryKey(product.getRawCategoryKey());
// Price + buy URL from offers
dto.setPrice(price);
dto.setBuyUrl(buyUrl);
return dto;
}
package group.goforward.ballistic.web.mapper;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.web.dto.ProductSummaryDto;
import java.math.BigDecimal;
public class ProductMapper {
public static ProductSummaryDto toSummary(Product product, BigDecimal price, String buyUrl) {
ProductSummaryDto dto = new ProductSummaryDto();
// Product ID -> String
dto.setId(String.valueOf(product.getId()));
dto.setName(product.getName());
dto.setBrand(product.getBrand() != null ? product.getBrand().getName() : null);
dto.setPlatform(product.getPlatform());
dto.setPartRole(product.getPartRole());
// Use rawCategoryKey from the Product entity
dto.setCategoryKey(product.getRawCategoryKey());
// Price + buy URL from offers
dto.setPrice(price);
dto.setBuyUrl(buyUrl);
return dto;
}
}