mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2025-12-06 02:56:44 -05:00
Compare commits
3 Commits
dont-lose-
...
d344b372d1
| Author | SHA1 | Date | |
|---|---|---|---|
| d344b372d1 | |||
| 31815d3145 | |||
| 4138edf45d |
34
docker/backend/Dockerfile
Normal file
34
docker/backend/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Stage 1: Build the application (The Build Stage)
|
||||||
|
# Use a Java SDK image with Maven pre-installed
|
||||||
|
FROM maven:3.9-jdk-17-slim AS build
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the Maven project files (pom.xml) first to leverage Docker layer caching
|
||||||
|
COPY pom.xml .
|
||||||
|
|
||||||
|
# Copy the source code
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# Build the Spring Boot application, skipping tests to speed up the Docker build
|
||||||
|
# This creates the executable JAR file in the 'target' directory
|
||||||
|
RUN mvn clean package -DskipTests
|
||||||
|
|
||||||
|
# Stage 2: Create the final lightweight image (The Runtime Stage)
|
||||||
|
# Use a smaller Java Runtime Environment (JRE) image for a smaller footprint
|
||||||
|
FROM openjdk:17-jre-slim
|
||||||
|
|
||||||
|
# Set the working directory in the final image
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the built JAR file from the 'build' stage into the final image
|
||||||
|
# The JAR file is typically named 'target/<your-app-name>-<version>.jar'
|
||||||
|
# You may need to adjust the name if you have a non-standard pom.xml
|
||||||
|
COPY --from=build /app/target/*.jar app.jar
|
||||||
|
|
||||||
|
# Expose the default Spring Boot port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Define the command to run the application
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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"]
|
|
||||||
@@ -2,20 +2,18 @@ version: '3.8'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
# --- 1. Spring API Service (Backend) ---
|
# --- 1. Spring API Service (Backend) ---
|
||||||
spring-api:
|
ss_builder-api:
|
||||||
build:
|
build:
|
||||||
context: ./backend # Path to your Spring project's root folder
|
context: ./backend # Path to your Spring project's root folder
|
||||||
dockerfile: Dockerfile # Assumes you have a Dockerfile in ./backend
|
dockerfile: Dockerfile # Assumes you have a Dockerfile in ./backend
|
||||||
container_name: spring-api
|
container_name: ss_builder-api
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080" # Map host port 8080 to container port 8080
|
- "8080:8080" # Map host port 8080 to container port 8080
|
||||||
environment:
|
environment:
|
||||||
# These environment variables link the API to the database service defined below
|
# These environment variables link the API to the database service defined below
|
||||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mydatabase
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://r710.dev.gofwd.group:5433/ss_builder
|
||||||
- SPRING_DATASOURCE_USERNAME=myuser
|
- SPRING_DATASOURCE_USERNAME=dba
|
||||||
- SPRING_DATASOURCE_PASSWORD=mypassword
|
- SPRING_DATASOURCE_PASSWORD=!@#Qwerty
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
@@ -24,38 +22,20 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./frontend # Path to your Next.js project's root folder
|
context: ./frontend # Path to your Next.js project's root folder
|
||||||
dockerfile: Dockerfile # Assumes you have a Dockerfile in ./frontend
|
dockerfile: Dockerfile # Assumes you have a Dockerfile in ./frontend
|
||||||
container_name: nextjs-app
|
container_name: ss_builder-app
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000" # Map host port 3000 to container port 3000
|
- "3000:3000" # Map host port 3000 to container port 3000
|
||||||
environment:
|
environment:
|
||||||
# This variable is crucial: Next.js needs the URL for the Spring API
|
# This variable is crucial: Next.js needs the URL for the Spring API
|
||||||
# Use the Docker internal service name 'spring-api' and its port 8080
|
# Use the Docker internal service name 'spring-api' and its port 8080
|
||||||
- NEXT_PUBLIC_API_URL=http://spring-api:8080
|
- NEXT_PUBLIC_API_URL=http://ss_builder-api:8080
|
||||||
# For local testing, you might need the host IP for Next.js to call back
|
# For local testing, you might need the host IP for Next.js to call back
|
||||||
# - NEXT_PUBLIC_API_URL_LOCAL=http://localhost:8080
|
# - NEXT_PUBLIC_API_URL_LOCAL=http://localhost:8080
|
||||||
depends_on:
|
depends_on:
|
||||||
- spring-api
|
- ss_builder-api
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
# --- 3. PostgreSQL Database Service (Example Dependency) ---
|
|
||||||
db:
|
|
||||||
image: postgres:15-alpine # Lightweight and stable PostgreSQL image
|
|
||||||
container_name: postgres-db
|
|
||||||
environment:
|
|
||||||
- POSTGRES_DB=mydatabase
|
|
||||||
- POSTGRES_USER=myuser
|
|
||||||
- POSTGRES_PASSWORD=mypassword
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data # Persist the database data
|
|
||||||
ports:
|
|
||||||
- "5432:5432" # Optional: Map DB port for external access (e.g., DBeaver)
|
|
||||||
networks:
|
|
||||||
- app-network
|
|
||||||
|
|
||||||
# --- Docker Volume for Persistent Data ---
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
|
|
||||||
# --- Docker Network for Inter-Container Communication ---
|
# --- Docker Network for Inter-Container Communication ---
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -30,13 +30,8 @@ public class CorsConfig {
|
|||||||
"https://localhost:8080",
|
"https://localhost:8080",
|
||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
"https://localhost:3000",
|
"https://localhost:3000",
|
||||||
"http://192.168.11.210:8070",
|
"https://localhost:3000/gunbuilder",
|
||||||
"https://192.168.11.210:8070",
|
"http://localhost:3000/gunbuilder"
|
||||||
"http://citysites.gofwd.group",
|
|
||||||
"https://citysites.gofwd.group",
|
|
||||||
"http://citysites.gofwd.group:8070",
|
|
||||||
"https://citysites.gofwd.group:8070"
|
|
||||||
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// Allow all headers
|
// Allow all headers
|
||||||
|
|||||||
@@ -24,19 +24,14 @@ public class SecurityConfig {
|
|||||||
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||||
)
|
)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
|
||||||
// Auth endpoints always open
|
// Auth endpoints always open
|
||||||
.requestMatchers("/api/auth/**").permitAll()
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
|
||||||
// Swagger / docs
|
// Swagger / docs
|
||||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
|
|
||||||
// Health
|
// Health
|
||||||
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
|
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
|
||||||
|
|
||||||
// Public product endpoints
|
// Public product endpoints
|
||||||
.requestMatchers("/api/products/gunbuilder/**").permitAll()
|
.requestMatchers("/api/products/gunbuilder/**").permitAll()
|
||||||
|
|
||||||
// Everything else (for now) also open – we can tighten later
|
// Everything else (for now) also open – we can tighten later
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package group.goforward.ballistic.controllers;
|
||||||
|
|
||||||
|
import group.goforward.ballistic.model.Brand;
|
||||||
|
import group.goforward.ballistic.model.State;
|
||||||
|
import group.goforward.ballistic.repos.BrandRepository;
|
||||||
|
import group.goforward.ballistic.services.BrandService;
|
||||||
|
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("/api/brands")
|
||||||
|
public class BrandController {
|
||||||
|
@Autowired
|
||||||
|
private BrandRepository repo;
|
||||||
|
@Autowired
|
||||||
|
private BrandService brandService;
|
||||||
|
//@Cacheable(value="getAllStates")
|
||||||
|
@GetMapping("/all")
|
||||||
|
public ResponseEntity<List<Brand>> getAllBrands() {
|
||||||
|
List<Brand> brand = repo.findAll();
|
||||||
|
return ResponseEntity.ok(brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<Brand> getAllBrandsById(@PathVariable Integer id) {
|
||||||
|
return repo.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/add")
|
||||||
|
public ResponseEntity<Brand> createbrand(@RequestBody Brand item) {
|
||||||
|
Brand created = brandService.save(item);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete/{id}")
|
||||||
|
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
|
||||||
|
return brandService.findById(id)
|
||||||
|
.map(item -> {
|
||||||
|
brandService.deleteById(id);
|
||||||
|
return ResponseEntity.noContent().<Void>build();
|
||||||
|
})
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package group.goforward.ballistic.controllers;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.repos.PartCategoryRepository;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.PartCategoryDto;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/categories")
|
|
||||||
@CrossOrigin // you can tighten origins later
|
|
||||||
public class CategoryController {
|
|
||||||
|
|
||||||
private final PartCategoryRepository partCategories;
|
|
||||||
|
|
||||||
public CategoryController(PartCategoryRepository partCategories) {
|
|
||||||
this.partCategories = partCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public List<PartCategoryDto> list() {
|
|
||||||
return partCategories.findAllByOrderByGroupNameAscSortOrderAscNameAsc()
|
|
||||||
.stream()
|
|
||||||
.map(pc -> new PartCategoryDto(
|
|
||||||
pc.getId(),
|
|
||||||
pc.getSlug(),
|
|
||||||
pc.getName(),
|
|
||||||
pc.getDescription(),
|
|
||||||
pc.getGroupName(),
|
|
||||||
pc.getSortOrder()
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import group.goforward.ballistic.model.State;
|
|||||||
import group.goforward.ballistic.repos.StateRepository;
|
import group.goforward.ballistic.repos.StateRepository;
|
||||||
import group.goforward.ballistic.services.StatesService;
|
import group.goforward.ballistic.services.StatesService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.cache.annotation.Cacheable;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -13,44 +14,38 @@ import java.util.List;
|
|||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping()
|
@RequestMapping("/api/states")
|
||||||
public class StateController {
|
public class StateController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private StateRepository repo;
|
private StateRepository repo;
|
||||||
@Autowired
|
@Autowired
|
||||||
private StatesService statesService;
|
private StatesService statesService;
|
||||||
|
//@Cacheable(value="getAllStates")
|
||||||
@GetMapping("/api/getAllStates")
|
@GetMapping("/all")
|
||||||
public ResponseEntity<List<State>> getAllStates() {
|
public ResponseEntity<List<State>> getAllStates() {
|
||||||
List<State> state = repo.findAll();
|
List<State> state = repo.findAll();
|
||||||
return ResponseEntity.ok(state);
|
return ResponseEntity.ok(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/api/getAllStatesTest")
|
@GetMapping("/{id}")
|
||||||
public ApiResponse<List<State>> getAllStatesTest() {
|
|
||||||
List<State> state = repo.findAll();
|
|
||||||
return ApiResponse.success(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/api/getAllStatesById/{id}")
|
|
||||||
public ResponseEntity<State> getAllStatesById(@PathVariable Integer id) {
|
public ResponseEntity<State> getAllStatesById(@PathVariable Integer id) {
|
||||||
return repo.findById(id)
|
return repo.findById(id)
|
||||||
.map(ResponseEntity::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElse(ResponseEntity.notFound().build());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
@GetMapping("/api/getAllStatesByAbbreviation/{abbreviation}")
|
@GetMapping("/byAbbrev/{abbreviation}")
|
||||||
public ResponseEntity<State> getAllStatesByAbbreviation(@PathVariable String abbreviation) {
|
public ResponseEntity<State> getAllStatesByAbbreviation(@PathVariable String abbreviation) {
|
||||||
return repo.findByAbbreviation(abbreviation)
|
return repo.findByAbbreviation(abbreviation)
|
||||||
.map(ResponseEntity::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElse(ResponseEntity.notFound().build());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
@PostMapping("/api/addState")
|
@PostMapping("/addState")
|
||||||
public ResponseEntity<State> createState(@RequestBody State item) {
|
public ResponseEntity<State> createState(@RequestBody State item) {
|
||||||
State created = statesService.save(item);
|
State created = statesService.save(item);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/api/deleteState/{id}")
|
@DeleteMapping("/deleteState/{id}")
|
||||||
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
|
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
|
||||||
return statesService.findById(id)
|
return statesService.findById(id)
|
||||||
.map(item -> {
|
.map(item -> {
|
||||||
|
|||||||
@@ -12,33 +12,36 @@ import java.util.List;
|
|||||||
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping()
|
@RequestMapping("/api/user")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
@Autowired
|
private final UserRepository repo;
|
||||||
private UserRepository repo;
|
private final UsersService usersService;
|
||||||
@Autowired
|
|
||||||
private UsersService usersService;
|
|
||||||
|
|
||||||
@GetMapping("/api/getAllUsers")
|
public UserController(UserRepository repo, UsersService usersService) {
|
||||||
|
this.repo = repo;
|
||||||
|
this.usersService = usersService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/all")
|
||||||
public ResponseEntity<List<User>> getAllUsers() {
|
public ResponseEntity<List<User>> getAllUsers() {
|
||||||
List<User> data = repo.findAll();
|
List<User> data = repo.findAll();
|
||||||
return ResponseEntity.ok(data);
|
return ResponseEntity.ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping("/api/getAllUsersById/{id}")
|
@GetMapping("/byId/{id}")
|
||||||
public ResponseEntity<User> getAllStatesById(@PathVariable Integer id) {
|
public ResponseEntity<User> getAllStatesById(@PathVariable Integer id) {
|
||||||
return repo.findById(id)
|
return repo.findById(id)
|
||||||
.map(ResponseEntity::ok)
|
.map(ResponseEntity::ok)
|
||||||
.orElse(ResponseEntity.notFound().build());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
@PostMapping("/api/addUser")
|
@PostMapping("/addUser")
|
||||||
public ResponseEntity<User> createUser(@RequestBody User item) {
|
public ResponseEntity<User> createUser(@RequestBody User item) {
|
||||||
User created = usersService.save(item);
|
User created = usersService.save(item);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
return ResponseEntity.status(HttpStatus.CREATED).body(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/api/deleteUser/{id}")
|
@DeleteMapping("/deleteUser/{id}")
|
||||||
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
|
public ResponseEntity<Void> deleteItem(@PathVariable Integer id) {
|
||||||
return usersService.findById(id)
|
return usersService.findById(id)
|
||||||
.map(item -> {
|
.map(item -> {
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package group.goforward.ballistic.controllers.admin;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
|
||||||
import group.goforward.ballistic.repos.PartCategoryRepository;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.PartCategoryDto;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/categories")
|
|
||||||
@CrossOrigin
|
|
||||||
public class AdminCategoryController {
|
|
||||||
|
|
||||||
private final PartCategoryRepository partCategories;
|
|
||||||
|
|
||||||
public AdminCategoryController(PartCategoryRepository partCategories) {
|
|
||||||
this.partCategories = partCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public List<PartCategoryDto> listCategories() {
|
|
||||||
return partCategories
|
|
||||||
.findAllByOrderByGroupNameAscSortOrderAscNameAsc()
|
|
||||||
.stream()
|
|
||||||
.map(this::toDto)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PartCategoryDto toDto(PartCategory entity) {
|
|
||||||
return new PartCategoryDto(
|
|
||||||
entity.getId(),
|
|
||||||
entity.getSlug(),
|
|
||||||
entity.getName(),
|
|
||||||
entity.getDescription(),
|
|
||||||
entity.getGroupName(),
|
|
||||||
entity.getSortOrder()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package group.goforward.ballistic.controllers.admin;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.CategoryMapping;
|
|
||||||
import group.goforward.ballistic.model.Merchant;
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
|
||||||
import group.goforward.ballistic.repos.CategoryMappingRepository;
|
|
||||||
import group.goforward.ballistic.repos.MerchantRepository;
|
|
||||||
import group.goforward.ballistic.repos.PartCategoryRepository;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.MerchantCategoryMappingDto;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.SimpleMerchantDto;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.UpdateMerchantCategoryMappingRequest;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/category-mappings")
|
|
||||||
@CrossOrigin // you can tighten origins later
|
|
||||||
public class AdminCategoryMappingController {
|
|
||||||
|
|
||||||
private final CategoryMappingRepository categoryMappingRepository;
|
|
||||||
private final MerchantRepository merchantRepository;
|
|
||||||
private final PartCategoryRepository partCategoryRepository;
|
|
||||||
|
|
||||||
public AdminCategoryMappingController(
|
|
||||||
CategoryMappingRepository categoryMappingRepository,
|
|
||||||
MerchantRepository merchantRepository,
|
|
||||||
PartCategoryRepository partCategoryRepository
|
|
||||||
) {
|
|
||||||
this.categoryMappingRepository = categoryMappingRepository;
|
|
||||||
this.merchantRepository = merchantRepository;
|
|
||||||
this.partCategoryRepository = partCategoryRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merchants that have at least one category_mappings row.
|
|
||||||
* Used for the "All Merchants" dropdown in the UI.
|
|
||||||
*/
|
|
||||||
@GetMapping("/merchants")
|
|
||||||
public List<SimpleMerchantDto> listMerchantsWithMappings() {
|
|
||||||
List<Merchant> merchants = categoryMappingRepository.findDistinctMerchantsWithMappings();
|
|
||||||
return merchants.stream()
|
|
||||||
.map(m -> new SimpleMerchantDto(m.getId(), m.getName()))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List mappings for a specific merchant, or all mappings if no merchantId is provided.
|
|
||||||
* GET /api/admin/category-mappings?merchantId=1
|
|
||||||
*/
|
|
||||||
@GetMapping
|
|
||||||
public List<MerchantCategoryMappingDto> listByMerchant(
|
|
||||||
@RequestParam(name = "merchantId", required = false) Integer merchantId
|
|
||||||
) {
|
|
||||||
List<CategoryMapping> mappings;
|
|
||||||
|
|
||||||
if (merchantId != null) {
|
|
||||||
mappings = categoryMappingRepository.findByMerchantIdOrderByRawCategoryPathAsc(merchantId);
|
|
||||||
} else {
|
|
||||||
// fall back to all mappings; you can add a more specific repository method later if desired
|
|
||||||
mappings = categoryMappingRepository.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappings.stream()
|
|
||||||
.map(cm -> new MerchantCategoryMappingDto(
|
|
||||||
cm.getId(),
|
|
||||||
cm.getMerchant().getId(),
|
|
||||||
cm.getMerchant().getName(),
|
|
||||||
cm.getRawCategoryPath(),
|
|
||||||
cm.getPartCategory() != null ? cm.getPartCategory().getId() : null,
|
|
||||||
cm.getPartCategory() != null ? cm.getPartCategory().getName() : null
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a single mapping's part_category.
|
|
||||||
* POST /api/admin/category-mappings/{id}
|
|
||||||
* Body: { "partCategoryId": 24 }
|
|
||||||
*/
|
|
||||||
@PostMapping("/{id}")
|
|
||||||
public MerchantCategoryMappingDto updateMapping(
|
|
||||||
@PathVariable Integer id,
|
|
||||||
@RequestBody UpdateMerchantCategoryMappingRequest request
|
|
||||||
) {
|
|
||||||
CategoryMapping mapping = categoryMappingRepository.findById(id)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Mapping not found"));
|
|
||||||
|
|
||||||
PartCategory partCategory = null;
|
|
||||||
if (request.partCategoryId() != null) {
|
|
||||||
partCategory = partCategoryRepository.findById(request.partCategoryId())
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Part category not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping.setPartCategory(partCategory);
|
|
||||||
mapping = categoryMappingRepository.save(mapping);
|
|
||||||
|
|
||||||
return new MerchantCategoryMappingDto(
|
|
||||||
mapping.getId(),
|
|
||||||
mapping.getMerchant().getId(),
|
|
||||||
mapping.getMerchant().getName(),
|
|
||||||
mapping.getRawCategoryPath(),
|
|
||||||
mapping.getPartCategory() != null ? mapping.getPartCategory().getId() : null,
|
|
||||||
mapping.getPartCategory() != null ? mapping.getPartCategory().getName() : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@PutMapping("/{id}")
|
|
||||||
public MerchantCategoryMappingDto updateMappingPut(
|
|
||||||
@PathVariable Integer id,
|
|
||||||
@RequestBody UpdateMerchantCategoryMappingRequest request
|
|
||||||
) {
|
|
||||||
// just delegate so POST & PUT behave the same
|
|
||||||
return updateMapping(id, request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package group.goforward.ballistic.controllers.admin;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
|
||||||
import group.goforward.ballistic.model.PartRoleMapping;
|
|
||||||
import group.goforward.ballistic.repos.PartCategoryRepository;
|
|
||||||
import group.goforward.ballistic.repos.PartRoleMappingRepository;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.AdminPartRoleMappingDto;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.CreatePartRoleMappingRequest;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.UpdatePartRoleMappingRequest;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/part-role-mappings")
|
|
||||||
@CrossOrigin
|
|
||||||
public class AdminPartRoleMappingController {
|
|
||||||
|
|
||||||
private final PartRoleMappingRepository partRoleMappingRepository;
|
|
||||||
private final PartCategoryRepository partCategoryRepository;
|
|
||||||
|
|
||||||
public AdminPartRoleMappingController(
|
|
||||||
PartRoleMappingRepository partRoleMappingRepository,
|
|
||||||
PartCategoryRepository partCategoryRepository
|
|
||||||
) {
|
|
||||||
this.partRoleMappingRepository = partRoleMappingRepository;
|
|
||||||
this.partCategoryRepository = partCategoryRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /api/admin/part-role-mappings?platform=AR-15
|
|
||||||
@GetMapping
|
|
||||||
public List<AdminPartRoleMappingDto> list(
|
|
||||||
@RequestParam(name = "platform", required = false) String platform
|
|
||||||
) {
|
|
||||||
List<PartRoleMapping> mappings;
|
|
||||||
|
|
||||||
if (platform != null && !platform.isBlank()) {
|
|
||||||
mappings = partRoleMappingRepository.findByPlatformOrderByPartRoleAsc(platform);
|
|
||||||
} else {
|
|
||||||
mappings = partRoleMappingRepository.findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappings.stream()
|
|
||||||
.map(this::toDto)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/admin/part-role-mappings
|
|
||||||
@PostMapping
|
|
||||||
public AdminPartRoleMappingDto create(
|
|
||||||
@RequestBody CreatePartRoleMappingRequest request
|
|
||||||
) {
|
|
||||||
PartCategory category = partCategoryRepository.findBySlug(request.categorySlug())
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"PartCategory not found for slug: " + request.categorySlug()
|
|
||||||
));
|
|
||||||
|
|
||||||
PartRoleMapping mapping = new PartRoleMapping();
|
|
||||||
mapping.setPlatform(request.platform());
|
|
||||||
mapping.setPartRole(request.partRole());
|
|
||||||
mapping.setPartCategory(category);
|
|
||||||
mapping.setNotes(request.notes());
|
|
||||||
|
|
||||||
mapping = partRoleMappingRepository.save(mapping);
|
|
||||||
return toDto(mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT /api/admin/part-role-mappings/{id}
|
|
||||||
@PutMapping("/{id}")
|
|
||||||
public AdminPartRoleMappingDto update(
|
|
||||||
@PathVariable Integer id,
|
|
||||||
@RequestBody UpdatePartRoleMappingRequest request
|
|
||||||
) {
|
|
||||||
PartRoleMapping mapping = partRoleMappingRepository.findById(id)
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Mapping not found"));
|
|
||||||
|
|
||||||
if (request.platform() != null) {
|
|
||||||
mapping.setPlatform(request.platform());
|
|
||||||
}
|
|
||||||
if (request.partRole() != null) {
|
|
||||||
mapping.setPartRole(request.partRole());
|
|
||||||
}
|
|
||||||
if (request.categorySlug() != null) {
|
|
||||||
PartCategory category = partCategoryRepository.findBySlug(request.categorySlug())
|
|
||||||
.orElseThrow(() -> new ResponseStatusException(
|
|
||||||
HttpStatus.BAD_REQUEST,
|
|
||||||
"PartCategory not found for slug: " + request.categorySlug()
|
|
||||||
));
|
|
||||||
mapping.setPartCategory(category);
|
|
||||||
}
|
|
||||||
if (request.notes() != null) {
|
|
||||||
mapping.setNotes(request.notes());
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping = partRoleMappingRepository.save(mapping);
|
|
||||||
return toDto(mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE /api/admin/part-role-mappings/{id}
|
|
||||||
@DeleteMapping("/{id}")
|
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
|
||||||
public void delete(@PathVariable Integer id) {
|
|
||||||
if (!partRoleMappingRepository.existsById(id)) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Mapping not found");
|
|
||||||
}
|
|
||||||
partRoleMappingRepository.deleteById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdminPartRoleMappingDto toDto(PartRoleMapping mapping) {
|
|
||||||
PartCategory cat = mapping.getPartCategory();
|
|
||||||
return new AdminPartRoleMappingDto(
|
|
||||||
mapping.getId(),
|
|
||||||
mapping.getPlatform(),
|
|
||||||
mapping.getPartRole(),
|
|
||||||
cat != null ? cat.getSlug() : null,
|
|
||||||
cat != null ? cat.getGroupName() : null,
|
|
||||||
mapping.getNotes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package group.goforward.ballistic.controllers.admin;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
|
||||||
import group.goforward.ballistic.repos.PartCategoryRepository;
|
|
||||||
import group.goforward.ballistic.web.dto.admin.PartCategoryDto;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/admin/part-categories")
|
|
||||||
@CrossOrigin // keep it loose for now, you can tighten origins later
|
|
||||||
public class PartCategoryAdminController {
|
|
||||||
|
|
||||||
private final PartCategoryRepository partCategories;
|
|
||||||
|
|
||||||
public PartCategoryAdminController(PartCategoryRepository partCategories) {
|
|
||||||
this.partCategories = partCategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public List<PartCategoryDto> list() {
|
|
||||||
return partCategories.findAllByOrderByGroupNameAscSortOrderAscNameAsc()
|
|
||||||
.stream()
|
|
||||||
.map(pc -> new PartCategoryDto(
|
|
||||||
pc.getId(),
|
|
||||||
pc.getSlug(),
|
|
||||||
pc.getName(),
|
|
||||||
pc.getDescription(),
|
|
||||||
pc.getGroupName(),
|
|
||||||
pc.getSortOrder()
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,32 +5,23 @@ import jakarta.persistence.*;
|
|||||||
@Entity
|
@Entity
|
||||||
@Table(name = "affiliate_category_map")
|
@Table(name = "affiliate_category_map")
|
||||||
public class AffiliateCategoryMap {
|
public class AffiliateCategoryMap {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
// e.g. "PART_ROLE", "RAW_CATEGORY", etc.
|
@Column(name = "feedname", nullable = false, length = 100)
|
||||||
@Column(name = "source_type", nullable = false)
|
private String feedname;
|
||||||
private String sourceType;
|
|
||||||
|
|
||||||
// the value we’re mapping from (e.g. "suppressor", "TRIGGER")
|
@Column(name = "affiliatecategory", nullable = false)
|
||||||
@Column(name = "source_value", nullable = false)
|
private String affiliatecategory;
|
||||||
private String sourceValue;
|
|
||||||
|
|
||||||
// optional platform ("AR-15", "PRECISION", etc.)
|
@Column(name = "buildercategoryid", nullable = false)
|
||||||
@Column(name = "platform")
|
private Integer buildercategoryid;
|
||||||
private String platform;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "part_category_id", nullable = false)
|
|
||||||
private PartCategory partCategory;
|
|
||||||
|
|
||||||
@Column(name = "notes")
|
@Column(name = "notes")
|
||||||
private String notes;
|
private String notes;
|
||||||
|
|
||||||
// --- getters / setters ---
|
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -39,36 +30,28 @@ public class AffiliateCategoryMap {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceType() {
|
public String getFeedname() {
|
||||||
return sourceType;
|
return feedname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSourceType(String sourceType) {
|
public void setFeedname(String feedname) {
|
||||||
this.sourceType = sourceType;
|
this.feedname = feedname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSourceValue() {
|
public String getAffiliatecategory() {
|
||||||
return sourceValue;
|
return affiliatecategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSourceValue(String sourceValue) {
|
public void setAffiliatecategory(String affiliatecategory) {
|
||||||
this.sourceValue = sourceValue;
|
this.affiliatecategory = affiliatecategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPlatform() {
|
public Integer getBuildercategoryid() {
|
||||||
return platform;
|
return buildercategoryid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlatform(String platform) {
|
public void setBuildercategoryid(Integer buildercategoryid) {
|
||||||
this.platform = platform;
|
this.buildercategoryid = buildercategoryid;
|
||||||
}
|
|
||||||
|
|
||||||
public PartCategory getPartCategory() {
|
|
||||||
return partCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPartCategory(PartCategory partCategory) {
|
|
||||||
this.partCategory = partCategory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNotes() {
|
public String getNotes() {
|
||||||
@@ -78,4 +61,5 @@ public class AffiliateCategoryMap {
|
|||||||
public void setNotes(String notes) {
|
public void setNotes(String notes) {
|
||||||
this.notes = notes;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
// src/main/java/group/goforward/ballistic/model/CategoryMapping.java
|
|
||||||
package group.goforward.ballistic.model;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "category_mappings")
|
|
||||||
public class CategoryMapping {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
@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_path", nullable = false)
|
|
||||||
private String rawCategoryPath;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "part_category_id")
|
|
||||||
private PartCategory partCategory;
|
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt = OffsetDateTime.now();
|
|
||||||
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt = OffsetDateTime.now();
|
|
||||||
|
|
||||||
@PrePersist
|
|
||||||
public void onCreate() {
|
|
||||||
OffsetDateTime now = OffsetDateTime.now();
|
|
||||||
if (createdAt == null) {
|
|
||||||
createdAt = now;
|
|
||||||
}
|
|
||||||
if (updatedAt == null) {
|
|
||||||
updatedAt = 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 getRawCategoryPath() {
|
|
||||||
return rawCategoryPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRawCategoryPath(String rawCategoryPath) {
|
|
||||||
this.rawCategoryPath = rawCategoryPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartCategory getPartCategory() {
|
|
||||||
return partCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPartCategory(PartCategory partCategory) {
|
|
||||||
this.partCategory = partCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +1,24 @@
|
|||||||
package group.goforward.ballistic.model;
|
package group.goforward.ballistic.model;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "part_categories")
|
@Table(name = "part_categories")
|
||||||
public class PartCategory {
|
public class PartCategory {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(name = "id", nullable = false)
|
@Column(name = "id", nullable = false)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@Column(name = "slug", nullable = false, unique = true)
|
@Column(name = "slug", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
@Column(name = "name", nullable = false)
|
@Column(name = "name", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Column(name = "description")
|
@Column(name = "description", length = Integer.MAX_VALUE)
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@ColumnDefault("gen_random_uuid()")
|
|
||||||
@Column(name = "uuid", nullable = false)
|
|
||||||
private UUID uuid;
|
|
||||||
|
|
||||||
@Column(name = "group_name")
|
|
||||||
private String groupName;
|
|
||||||
|
|
||||||
@Column(name = "sort_order")
|
|
||||||
private Integer sortOrder;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@ColumnDefault("now()")
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
|
|
||||||
// --- Getters & Setters ---
|
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -76,43 +51,4 @@ public class PartCategory {
|
|||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getUuid() {
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUuid(UUID uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGroupName() {
|
|
||||||
return groupName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupName(String groupName) {
|
|
||||||
this.groupName = groupName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getSortOrder() {
|
|
||||||
return sortOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSortOrder(Integer sortOrder) {
|
|
||||||
this.sortOrder = sortOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package group.goforward.ballistic.model;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "part_role_category_mappings",
|
|
||||||
uniqueConstraints = @UniqueConstraint(columnNames = {"platform", "part_role"}))
|
|
||||||
public class PartRoleCategoryMapping {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@Column(name = "platform", nullable = false)
|
|
||||||
private String platform;
|
|
||||||
|
|
||||||
@Column(name = "part_role", nullable = false)
|
|
||||||
private String partRole;
|
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "category_slug", referencedColumnName = "slug", nullable = false)
|
|
||||||
private PartCategory category;
|
|
||||||
|
|
||||||
@Column(name = "notes")
|
|
||||||
private String notes;
|
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false)
|
|
||||||
private OffsetDateTime createdAt;
|
|
||||||
|
|
||||||
@Column(name = "updated_at", nullable = false)
|
|
||||||
private OffsetDateTime updatedAt;
|
|
||||||
|
|
||||||
// getters/setters…
|
|
||||||
|
|
||||||
public Integer getId() { return id; }
|
|
||||||
public void setId(Integer id) { this.id = id; }
|
|
||||||
|
|
||||||
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 PartCategory getCategory() { return category; }
|
|
||||||
public void setCategory(PartCategory category) { this.category = category; }
|
|
||||||
|
|
||||||
public String getNotes() { return notes; }
|
|
||||||
public void setNotes(String notes) { this.notes = notes; }
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package group.goforward.ballistic.model;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
@Table(name = "part_role_mappings")
|
|
||||||
public class PartRoleMapping {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private String platform; // e.g. "AR-15"
|
|
||||||
|
|
||||||
@Column(name = "part_role", nullable = false)
|
|
||||||
private String partRole; // e.g. "UPPER", "BARREL", etc.
|
|
||||||
|
|
||||||
@ManyToOne(optional = false)
|
|
||||||
@JoinColumn(name = "part_category_id")
|
|
||||||
private PartCategory partCategory;
|
|
||||||
|
|
||||||
@Column(columnDefinition = "text")
|
|
||||||
private String notes;
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 PartCategory getPartCategory() {
|
|
||||||
return partCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPartCategory(PartCategory partCategory) {
|
|
||||||
this.partCategory = partCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotes() {
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNotes(String notes) {
|
|
||||||
this.notes = notes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,7 @@ package group.goforward.ballistic.model;
|
|||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.ProductOffer;
|
|
||||||
import group.goforward.ballistic.model.ProductConfiguration;
|
import group.goforward.ballistic.model.ProductConfiguration;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -74,16 +68,7 @@ public class Product {
|
|||||||
@Column(name = "platform_locked", nullable = false)
|
@Column(name = "platform_locked", nullable = false)
|
||||||
private Boolean platformLocked = false;
|
private Boolean platformLocked = false;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
|
|
||||||
private Set<ProductOffer> offers = new HashSet<>();
|
|
||||||
|
|
||||||
public Set<ProductOffer> getOffers() {
|
|
||||||
return offers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOffers(Set<ProductOffer> offers) {
|
|
||||||
this.offers = offers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- lifecycle hooks ---
|
// --- lifecycle hooks ---
|
||||||
|
|
||||||
@@ -251,41 +236,4 @@ public class Product {
|
|||||||
public void setConfiguration(ProductConfiguration configuration) {
|
public void setConfiguration(ProductConfiguration configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
}
|
}
|
||||||
// Convenience: best offer price for Gunbuilder
|
|
||||||
public BigDecimal getBestOfferPrice() {
|
|
||||||
if (offers == null || offers.isEmpty()) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return offers.stream()
|
|
||||||
// pick sale_price if present, otherwise retail_price
|
|
||||||
.map(offer -> {
|
|
||||||
if (offer.getSalePrice() != null) {
|
|
||||||
return offer.getSalePrice();
|
|
||||||
}
|
|
||||||
return offer.getRetailPrice();
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.min(BigDecimal::compareTo)
|
|
||||||
.orElse(BigDecimal.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience: URL for the best-priced offer
|
|
||||||
public String getBestOfferBuyUrl() {
|
|
||||||
if (offers == null || offers.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return offers.stream()
|
|
||||||
.sorted(Comparator.comparing(offer -> {
|
|
||||||
if (offer.getSalePrice() != null) {
|
|
||||||
return offer.getSalePrice();
|
|
||||||
}
|
|
||||||
return offer.getRetailPrice();
|
|
||||||
}, Comparator.nullsLast(BigDecimal::compareTo)))
|
|
||||||
.map(ProductOffer::getAffiliateUrl)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import org.hibernate.annotations.OnDeleteAction;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "product_offers")
|
@Table(name = "product_offers")
|
||||||
public class ProductOffer {
|
public class ProductOffer {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(name = "id", nullable = false)
|
@Column(name = "id", nullable = false)
|
||||||
@@ -26,16 +26,16 @@ public class ProductOffer {
|
|||||||
@JoinColumn(name = "merchant_id", nullable = false)
|
@JoinColumn(name = "merchant_id", nullable = false)
|
||||||
private Merchant merchant;
|
private Merchant merchant;
|
||||||
|
|
||||||
@Column(name = "avantlink_product_id", nullable = false)
|
@Column(name = "avantlink_product_id", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String avantlinkProductId;
|
private String avantlinkProductId;
|
||||||
|
|
||||||
@Column(name = "sku")
|
@Column(name = "sku", length = Integer.MAX_VALUE)
|
||||||
private String sku;
|
private String sku;
|
||||||
|
|
||||||
@Column(name = "upc")
|
@Column(name = "upc", length = Integer.MAX_VALUE)
|
||||||
private String upc;
|
private String upc;
|
||||||
|
|
||||||
@Column(name = "buy_url", nullable = false)
|
@Column(name = "buy_url", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String buyUrl;
|
private String buyUrl;
|
||||||
|
|
||||||
@Column(name = "price", nullable = false, precision = 10, scale = 2)
|
@Column(name = "price", nullable = false, precision = 10, scale = 2)
|
||||||
@@ -45,7 +45,7 @@ public class ProductOffer {
|
|||||||
private BigDecimal originalPrice;
|
private BigDecimal originalPrice;
|
||||||
|
|
||||||
@ColumnDefault("'USD'")
|
@ColumnDefault("'USD'")
|
||||||
@Column(name = "currency", nullable = false)
|
@Column(name = "currency", nullable = false, length = Integer.MAX_VALUE)
|
||||||
private String currency;
|
private String currency;
|
||||||
|
|
||||||
@ColumnDefault("true")
|
@ColumnDefault("true")
|
||||||
@@ -60,10 +60,6 @@ public class ProductOffer {
|
|||||||
@Column(name = "first_seen_at", nullable = false)
|
@Column(name = "first_seen_at", nullable = false)
|
||||||
private OffsetDateTime firstSeenAt;
|
private OffsetDateTime firstSeenAt;
|
||||||
|
|
||||||
// -----------------------------------------------------
|
|
||||||
// Getters & setters
|
|
||||||
// -----------------------------------------------------
|
|
||||||
|
|
||||||
public Integer getId() {
|
public Integer getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -168,26 +164,14 @@ public class ProductOffer {
|
|||||||
this.firstSeenAt = firstSeenAt;
|
this.firstSeenAt = firstSeenAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------
|
|
||||||
// Helper Methods (used by Product entity)
|
|
||||||
// -----------------------------------------------------
|
|
||||||
|
|
||||||
public BigDecimal getSalePrice() {
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getRetailPrice() {
|
|
||||||
return originalPrice != null ? originalPrice : price;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAffiliateUrl() {
|
|
||||||
return buyUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getEffectivePrice() {
|
public BigDecimal getEffectivePrice() {
|
||||||
|
// Prefer a true sale price when it's lower than the original
|
||||||
if (price != null && originalPrice != null && price.compareTo(originalPrice) < 0) {
|
if (price != null && originalPrice != null && price.compareTo(originalPrice) < 0) {
|
||||||
return price;
|
return price;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, use whatever is available
|
||||||
return price != null ? price : originalPrice;
|
return price != null ? price : originalPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,7 @@
|
|||||||
package group.goforward.ballistic.repos;
|
package group.goforward.ballistic.repos;
|
||||||
|
|
||||||
import group.goforward.ballistic.model.CategoryMapping;
|
import group.goforward.ballistic.model.AffiliateCategoryMap;
|
||||||
import group.goforward.ballistic.model.Merchant;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
|
|
||||||
import java.util.List;
|
public interface CategoryMappingRepository extends JpaRepository<AffiliateCategoryMap, Integer> {
|
||||||
|
|
||||||
public interface CategoryMappingRepository extends JpaRepository<CategoryMapping, Integer> {
|
|
||||||
|
|
||||||
// All mappings for a merchant, ordered nicely
|
|
||||||
List<CategoryMapping> findByMerchantIdOrderByRawCategoryPathAsc(Integer merchantId);
|
|
||||||
|
|
||||||
// Merchants that actually have mappings (for the dropdown)
|
|
||||||
@Query("""
|
|
||||||
select distinct cm.merchant
|
|
||||||
from CategoryMapping cm
|
|
||||||
order by cm.merchant.name asc
|
|
||||||
""")
|
|
||||||
List<Merchant> findDistinctMerchantsWithMappings();
|
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,8 @@ package group.goforward.ballistic.repos;
|
|||||||
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
import group.goforward.ballistic.model.PartCategory;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface PartCategoryRepository extends JpaRepository<PartCategory, Integer> {
|
public interface PartCategoryRepository extends JpaRepository<PartCategory, Integer> {
|
||||||
|
|
||||||
Optional<PartCategory> findBySlug(String slug);
|
Optional<PartCategory> findBySlug(String slug);
|
||||||
|
|
||||||
List<PartCategory> findAllByOrderByGroupNameAscSortOrderAscNameAsc();
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package group.goforward.ballistic.repos;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartRoleCategoryMapping;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public interface PartRoleCategoryMappingRepository extends JpaRepository<PartRoleCategoryMapping, Integer> {
|
|
||||||
|
|
||||||
List<PartRoleCategoryMapping> findAllByPlatformOrderByPartRoleAsc(String platform);
|
|
||||||
|
|
||||||
Optional<PartRoleCategoryMapping> findByPlatformAndPartRole(String platform, String partRole);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package group.goforward.ballistic.repos;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartRoleMapping;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface PartRoleMappingRepository extends JpaRepository<PartRoleMapping, Integer> {
|
|
||||||
|
|
||||||
// List mappings for a platform, ordered nicely for the UI
|
|
||||||
List<PartRoleMapping> findByPlatformOrderByPartRoleAsc(String platform);
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,33 @@
|
|||||||
package group.goforward.ballistic.repos;
|
package group.goforward.ballistic.repos;
|
||||||
|
|
||||||
import group.goforward.ballistic.model.Brand;
|
|
||||||
import group.goforward.ballistic.model.Product;
|
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.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
public interface ProductRepository extends JpaRepository<Product, Integer> {
|
public interface ProductRepository extends JpaRepository<Product, Integer> {
|
||||||
|
|
||||||
// -------------------------------------------------
|
Optional<Product> findByUuid(UUID uuid);
|
||||||
// Used by MerchantFeedImportServiceImpl
|
|
||||||
// -------------------------------------------------
|
boolean existsBySlug(String slug);
|
||||||
|
|
||||||
List<Product> findAllByBrandAndMpn(Brand brand, String mpn);
|
List<Product> findAllByBrandAndMpn(Brand brand, String mpn);
|
||||||
|
|
||||||
List<Product> findAllByBrandAndUpc(Brand brand, String upc);
|
List<Product> findAllByBrandAndUpc(Brand brand, String upc);
|
||||||
|
|
||||||
boolean existsBySlug(String slug);
|
// 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.)
|
||||||
// Used by ProductController for platform views
|
List<Product> findByPlatformAndPartRoleIn(String platform, Collection<String> partRoles);
|
||||||
// -------------------------------------------------
|
|
||||||
|
// ---------- Optimized variants for Gunbuilder (fetch brand to avoid N+1) ----------
|
||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT p
|
SELECT p
|
||||||
@@ -38,25 +43,11 @@ public interface ProductRepository extends JpaRepository<Product, Integer> {
|
|||||||
FROM Product p
|
FROM Product p
|
||||||
JOIN FETCH p.brand b
|
JOIN FETCH p.brand b
|
||||||
WHERE p.platform = :platform
|
WHERE p.platform = :platform
|
||||||
AND p.partRole IN :roles
|
AND p.partRole IN :partRoles
|
||||||
AND p.deletedAt IS NULL
|
AND p.deletedAt IS NULL
|
||||||
""")
|
""")
|
||||||
List<Product> findByPlatformAndPartRoleInWithBrand(
|
List<Product> findByPlatformAndPartRoleInWithBrand(
|
||||||
@Param("platform") String platform,
|
@Param("platform") String platform,
|
||||||
@Param("roles") List<String> roles
|
@Param("partRoles") Collection<String> partRoles
|
||||||
);
|
);
|
||||||
|
|
||||||
// -------------------------------------------------
|
|
||||||
// Used by Gunbuilder service (if you wired this)
|
|
||||||
// -------------------------------------------------
|
|
||||||
|
|
||||||
@Query("""
|
|
||||||
SELECT DISTINCT p
|
|
||||||
FROM Product p
|
|
||||||
LEFT JOIN FETCH p.brand b
|
|
||||||
LEFT JOIN FETCH p.offers o
|
|
||||||
WHERE p.platform = :platform
|
|
||||||
AND p.deletedAt IS NULL
|
|
||||||
""")
|
|
||||||
List<Product> findSomethingForGunbuilder(@Param("platform") String platform);
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package group.goforward.ballistic.services;
|
||||||
|
|
||||||
|
import group.goforward.ballistic.model.Brand;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface BrandService {
|
||||||
|
|
||||||
|
List<Brand> findAll();
|
||||||
|
|
||||||
|
Optional<Brand> findById(Integer id);
|
||||||
|
|
||||||
|
Brand save(Brand item);
|
||||||
|
void deleteById(Integer id);
|
||||||
|
}
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package group.goforward.ballistic.services;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
|
||||||
import group.goforward.ballistic.model.Product;
|
|
||||||
import group.goforward.ballistic.repos.ProductRepository;
|
|
||||||
import group.goforward.ballistic.web.dto.GunbuilderProductDto;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class GunbuilderProductService {
|
|
||||||
|
|
||||||
private final ProductRepository productRepository;
|
|
||||||
private final PartCategoryResolverService partCategoryResolverService;
|
|
||||||
|
|
||||||
public GunbuilderProductService(
|
|
||||||
ProductRepository productRepository,
|
|
||||||
PartCategoryResolverService partCategoryResolverService
|
|
||||||
) {
|
|
||||||
this.productRepository = productRepository;
|
|
||||||
this.partCategoryResolverService = partCategoryResolverService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<GunbuilderProductDto> listGunbuilderProducts(String platform) {
|
|
||||||
|
|
||||||
List<Product> products = productRepository.findSomethingForGunbuilder(platform);
|
|
||||||
|
|
||||||
return products.stream()
|
|
||||||
.map(p -> {
|
|
||||||
var maybeCategory = partCategoryResolverService
|
|
||||||
.resolveForPlatformAndPartRole(platform, p.getPartRole());
|
|
||||||
|
|
||||||
if (maybeCategory.isEmpty()) {
|
|
||||||
// you can also log here
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
PartCategory cat = maybeCategory.get();
|
|
||||||
|
|
||||||
return new GunbuilderProductDto(
|
|
||||||
p.getId(),
|
|
||||||
p.getName(),
|
|
||||||
p.getBrand().getName(),
|
|
||||||
platform,
|
|
||||||
p.getPartRole(),
|
|
||||||
p.getBestOfferPrice(),
|
|
||||||
p.getMainImageUrl(),
|
|
||||||
p.getBestOfferBuyUrl(),
|
|
||||||
cat.getSlug(),
|
|
||||||
cat.getGroupName()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.filter(dto -> dto != null)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package group.goforward.ballistic.services;
|
|
||||||
|
|
||||||
import group.goforward.ballistic.model.PartCategory;
|
|
||||||
import group.goforward.ballistic.repos.PartCategoryRepository;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class PartCategoryResolverService {
|
|
||||||
|
|
||||||
private final PartCategoryRepository partCategoryRepository;
|
|
||||||
|
|
||||||
public PartCategoryResolverService(PartCategoryRepository partCategoryRepository) {
|
|
||||||
this.partCategoryRepository = partCategoryRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the canonical PartCategory for a given platform + partRole.
|
|
||||||
*
|
|
||||||
* For now we keep it simple:
|
|
||||||
* - We treat partRole as the slug (e.g. "barrel", "upper", "trigger").
|
|
||||||
* - Normalize to lower-kebab (spaces -> dashes, lowercased).
|
|
||||||
* - Look up by slug in part_categories.
|
|
||||||
*
|
|
||||||
* Later, if we want per-merchant / per-platform overrides using category_mappings,
|
|
||||||
* we can extend this method without changing callers.
|
|
||||||
*/
|
|
||||||
public Optional<PartCategory> resolveForPlatformAndPartRole(String platform, String partRole) {
|
|
||||||
if (partRole == null || partRole.isBlank()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
String normalizedSlug = partRole
|
|
||||||
.trim()
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(" ", "-");
|
|
||||||
|
|
||||||
return partCategoryRepository.findBySlug(normalizedSlug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package group.goforward.ballistic.services.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import group.goforward.ballistic.model.Brand;
|
||||||
|
import group.goforward.ballistic.repos.BrandRepository;
|
||||||
|
import group.goforward.ballistic.services.BrandService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class BrandServiceImpl implements BrandService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BrandRepository repo;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Brand> findAll() {
|
||||||
|
return repo.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Brand> findById(Integer id) {
|
||||||
|
return repo.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Brand save(Brand item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteById(Integer id) {
|
||||||
|
deleteById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public class GunbuilderProductDto {
|
|
||||||
|
|
||||||
private Integer id;
|
|
||||||
private String name;
|
|
||||||
private String brand;
|
|
||||||
private String platform;
|
|
||||||
private String partRole;
|
|
||||||
private BigDecimal price;
|
|
||||||
private String imageUrl;
|
|
||||||
private String buyUrl;
|
|
||||||
private String categorySlug;
|
|
||||||
private String categoryGroup;
|
|
||||||
|
|
||||||
public GunbuilderProductDto(
|
|
||||||
Integer id,
|
|
||||||
String name,
|
|
||||||
String brand,
|
|
||||||
String platform,
|
|
||||||
String partRole,
|
|
||||||
BigDecimal price,
|
|
||||||
String imageUrl,
|
|
||||||
String buyUrl,
|
|
||||||
String categorySlug,
|
|
||||||
String categoryGroup
|
|
||||||
) {
|
|
||||||
this.id = id;
|
|
||||||
this.name = name;
|
|
||||||
this.brand = brand;
|
|
||||||
this.platform = platform;
|
|
||||||
this.partRole = partRole;
|
|
||||||
this.price = price;
|
|
||||||
this.imageUrl = imageUrl;
|
|
||||||
this.buyUrl = buyUrl;
|
|
||||||
this.categorySlug = categorySlug;
|
|
||||||
this.categoryGroup = categoryGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Getters only (DTOs are read-only in most cases) ---
|
|
||||||
public Integer getId() { return id; }
|
|
||||||
public String getName() { return name; }
|
|
||||||
public String getBrand() { return brand; }
|
|
||||||
public String getPlatform() { return platform; }
|
|
||||||
public String getPartRole() { return partRole; }
|
|
||||||
public BigDecimal getPrice() { return price; }
|
|
||||||
public String getImageUrl() { return imageUrl; }
|
|
||||||
public String getBuyUrl() { return buyUrl; }
|
|
||||||
public String getCategorySlug() { return categorySlug; }
|
|
||||||
public String getCategoryGroup() { return categoryGroup; }
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record AdminPartRoleMappingDto(
|
|
||||||
Integer id,
|
|
||||||
String platform,
|
|
||||||
String partRole,
|
|
||||||
String categorySlug,
|
|
||||||
String groupName,
|
|
||||||
String notes
|
|
||||||
) {}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record CreatePartRoleMappingRequest(
|
|
||||||
String platform,
|
|
||||||
String partRole,
|
|
||||||
String categorySlug,
|
|
||||||
String notes
|
|
||||||
) {}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// src/main/java/group/goforward/ballistic/web/dto/admin/MerchantCategoryMappingDto.java
|
|
||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record MerchantCategoryMappingDto(
|
|
||||||
Integer id,
|
|
||||||
Integer merchantId,
|
|
||||||
String merchantName,
|
|
||||||
String rawCategoryPath,
|
|
||||||
Integer partCategoryId,
|
|
||||||
String partCategoryName
|
|
||||||
) {}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record PartCategoryDto(
|
|
||||||
Integer id,
|
|
||||||
String slug,
|
|
||||||
String name,
|
|
||||||
String description,
|
|
||||||
String groupName,
|
|
||||||
Integer sortOrder
|
|
||||||
) {}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record PartRoleMappingDto(
|
|
||||||
Integer id,
|
|
||||||
String platform,
|
|
||||||
String partRole,
|
|
||||||
String categorySlug,
|
|
||||||
String groupName,
|
|
||||||
String notes
|
|
||||||
) {}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record PartRoleMappingRequest(
|
|
||||||
String platform,
|
|
||||||
String partRole,
|
|
||||||
String categorySlug,
|
|
||||||
String notes
|
|
||||||
) {}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record SimpleMerchantDto(
|
|
||||||
Integer id,
|
|
||||||
String name
|
|
||||||
) { }
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record UpdateMerchantCategoryMappingRequest(
|
|
||||||
Integer partCategoryId
|
|
||||||
) {}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package group.goforward.ballistic.web.dto.admin;
|
|
||||||
|
|
||||||
public record UpdatePartRoleMappingRequest(
|
|
||||||
String platform,
|
|
||||||
String partRole,
|
|
||||||
String categorySlug,
|
|
||||||
String notes
|
|
||||||
) {}
|
|
||||||
Reference in New Issue
Block a user