mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2026-01-20 16:51:03 -05:00
fixes to admin/products
This commit is contained in:
@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/platforms")
|
@RequestMapping("/api/platforms")
|
||||||
@@ -102,4 +103,35 @@ public class AdminPlatformController {
|
|||||||
public ResponseEntity<Void> deleteLegacy(@PathVariable Integer id) {
|
public ResponseEntity<Void> deleteLegacy(@PathVariable Integer id) {
|
||||||
return delete(id);
|
return delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update platform (toggle active)
|
||||||
|
* PATCH /api/platforms/{id}
|
||||||
|
* PUT /api/platforms/{id}
|
||||||
|
* Body: { "isActive": true|false }
|
||||||
|
*/
|
||||||
|
@PatchMapping("/{id}")
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<PlatformDto> update(@PathVariable Integer id, @RequestBody PlatformUpdateRequest req) {
|
||||||
|
Platform platform = platformRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Platform not found"));
|
||||||
|
|
||||||
|
if (req != null && req.isActive() != null) {
|
||||||
|
platform.setIsActive(req.isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
platform.setUpdatedAt(OffsetDateTime.now());
|
||||||
|
Platform saved = platformRepository.save(platform);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new PlatformDto(
|
||||||
|
saved.getId(),
|
||||||
|
saved.getKey(),
|
||||||
|
saved.getLabel(),
|
||||||
|
saved.getCreatedAt(),
|
||||||
|
saved.getUpdatedAt(),
|
||||||
|
saved.getIsActive()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record PlatformUpdateRequest(Boolean isActive) {}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package group.goforward.battlbuilder.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "calibers")
|
||||||
|
public class Caliber {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
@Column(name = "is_active", nullable = false)
|
||||||
|
private Boolean isActive = true;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false)
|
||||||
|
private OffsetDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private OffsetDateTime updatedAt;
|
||||||
|
|
||||||
|
@Column(name = "deleted_at")
|
||||||
|
private OffsetDateTime deletedAt;
|
||||||
|
|
||||||
|
// --- getters/setters ---
|
||||||
|
|
||||||
|
public Integer getId() { return id; }
|
||||||
|
public void setId(Integer id) { this.id = id; }
|
||||||
|
|
||||||
|
public String getKey() { return key; }
|
||||||
|
public void setKey(String key) { this.key = key; }
|
||||||
|
|
||||||
|
public String getLabel() { return label; }
|
||||||
|
public void setLabel(String label) { this.label = label; }
|
||||||
|
|
||||||
|
public Boolean getIsActive() { return isActive; }
|
||||||
|
public void setIsActive(Boolean active) { isActive = active; }
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public OffsetDateTime getDeletedAt() { return deletedAt; }
|
||||||
|
public void setDeletedAt(OffsetDateTime deletedAt) { this.deletedAt = deletedAt; }
|
||||||
|
|
||||||
|
// --- lifecycle hooks (prevents null timestamp issues) ---
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
OffsetDateTime now = OffsetDateTime.now();
|
||||||
|
if (createdAt == null) createdAt = now;
|
||||||
|
if (updatedAt == null) updatedAt = now;
|
||||||
|
if (isActive == null) isActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
protected void onUpdate() {
|
||||||
|
updatedAt = OffsetDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package group.goforward.battlbuilder.repos;
|
||||||
|
|
||||||
|
import group.goforward.battlbuilder.model.Caliber;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface CaliberRepository extends JpaRepository<Caliber, Integer> {
|
||||||
|
List<Caliber> findAllByDeletedAtIsNullAndIsActiveTrueOrderByLabelAsc();
|
||||||
|
List<Caliber> findAllByDeletedAtIsNullOrderByLabelAsc();
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package group.goforward.battlbuilder.services.admin.impl;
|
package group.goforward.battlbuilder.services.admin.impl;
|
||||||
|
|
||||||
|
import group.goforward.battlbuilder.model.Caliber;
|
||||||
import group.goforward.battlbuilder.model.Product;
|
import group.goforward.battlbuilder.model.Product;
|
||||||
import group.goforward.battlbuilder.model.enums.PartRoleSource;
|
import group.goforward.battlbuilder.model.enums.PartRoleSource;
|
||||||
|
import group.goforward.battlbuilder.repos.CaliberRepository;
|
||||||
import group.goforward.battlbuilder.repos.ProductRepository;
|
import group.goforward.battlbuilder.repos.ProductRepository;
|
||||||
import group.goforward.battlbuilder.services.admin.AdminProductService;
|
import group.goforward.battlbuilder.services.admin.AdminProductService;
|
||||||
import group.goforward.battlbuilder.domain.specs.ProductSpecifications;
|
import group.goforward.battlbuilder.domain.specs.ProductSpecifications;
|
||||||
@@ -36,9 +38,11 @@ import java.time.Instant;
|
|||||||
public class AdminProductServiceImpl implements AdminProductService {
|
public class AdminProductServiceImpl implements AdminProductService {
|
||||||
|
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
|
private final CaliberRepository caliberRepository;
|
||||||
|
|
||||||
public AdminProductServiceImpl(ProductRepository productRepository) {
|
public AdminProductServiceImpl(ProductRepository productRepository, CaliberRepository caliberRepository) {
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
|
this.caliberRepository = caliberRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -241,6 +245,17 @@ public class AdminProductServiceImpl implements AdminProductService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public List<String> distinctCalibers(AdminProductSearchRequest request) {
|
public List<String> distinctCalibers(AdminProductSearchRequest request) {
|
||||||
|
// Prefer curated calibers if present (table-based)
|
||||||
|
List<Caliber> curated = caliberRepository.findAllByDeletedAtIsNullAndIsActiveTrueOrderByLabelAsc();
|
||||||
|
if (curated != null && !curated.isEmpty()) {
|
||||||
|
return curated.stream()
|
||||||
|
.map(Caliber::getLabel)
|
||||||
|
.filter(s -> s != null && !s.trim().isEmpty())
|
||||||
|
.map(String::trim)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback (legacy): compute distinct calibers from products using the same filter spec
|
||||||
var spec = ProductSpecifications.adminSearch(request);
|
var spec = ProductSpecifications.adminSearch(request);
|
||||||
|
|
||||||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package group.goforward.battlbuilder.web.admin;
|
||||||
|
|
||||||
|
import group.goforward.battlbuilder.model.Caliber;
|
||||||
|
import group.goforward.battlbuilder.repos.CaliberRepository;
|
||||||
|
import group.goforward.battlbuilder.web.dto.admin.CaliberDto;
|
||||||
|
import group.goforward.battlbuilder.web.dto.admin.CaliberUpsertRequest;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/admin/calibers")
|
||||||
|
public class AdminCaliberController {
|
||||||
|
|
||||||
|
private final CaliberRepository caliberRepository;
|
||||||
|
|
||||||
|
public AdminCaliberController(CaliberRepository caliberRepository) {
|
||||||
|
this.caliberRepository = caliberRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<CaliberDto> list(
|
||||||
|
@RequestParam(name = "activeOnly", defaultValue = "true") boolean activeOnly
|
||||||
|
) {
|
||||||
|
var list = activeOnly
|
||||||
|
? caliberRepository.findAllByDeletedAtIsNullAndIsActiveTrueOrderByLabelAsc()
|
||||||
|
: caliberRepository.findAllByDeletedAtIsNullOrderByLabelAsc();
|
||||||
|
|
||||||
|
return list.stream()
|
||||||
|
.map(this::toDto)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<CaliberDto> create(@RequestBody CaliberUpsertRequest req) {
|
||||||
|
Caliber c = new Caliber();
|
||||||
|
|
||||||
|
if (req == null || req.key() == null || req.key().trim().isEmpty()) {
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = normalizeKey(req.key());
|
||||||
|
c.setKey(key);
|
||||||
|
|
||||||
|
String label = (req.label() == null || req.label().trim().isEmpty())
|
||||||
|
? key
|
||||||
|
: req.label().trim();
|
||||||
|
c.setLabel(label);
|
||||||
|
|
||||||
|
c.setIsActive(req.isActive() == null ? true : req.isActive());
|
||||||
|
|
||||||
|
c.setCreatedAt(OffsetDateTime.now());
|
||||||
|
c.setUpdatedAt(OffsetDateTime.now());
|
||||||
|
|
||||||
|
Caliber saved = caliberRepository.save(c);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(toDto(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<CaliberDto> update(@PathVariable Integer id, @RequestBody CaliberUpsertRequest req) {
|
||||||
|
var existingOpt = caliberRepository.findById(id);
|
||||||
|
if (existingOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
|
Caliber c = existingOpt.get();
|
||||||
|
if (c.getDeletedAt() != null) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
|
if (req.key() != null && !req.key().trim().isEmpty()) {
|
||||||
|
c.setKey(normalizeKey(req.key()));
|
||||||
|
}
|
||||||
|
if (req.label() != null) {
|
||||||
|
String label = req.label().trim();
|
||||||
|
c.setLabel(label.isEmpty() ? c.getKey() : label);
|
||||||
|
}
|
||||||
|
if (req.isActive() != null) {
|
||||||
|
c.setIsActive(req.isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
c.setUpdatedAt(OffsetDateTime.now());
|
||||||
|
|
||||||
|
Caliber saved = caliberRepository.save(c);
|
||||||
|
return ResponseEntity.ok(toDto(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{id}/active")
|
||||||
|
public ResponseEntity<CaliberDto> setActive(@PathVariable Integer id, @RequestParam boolean active) {
|
||||||
|
var existingOpt = caliberRepository.findById(id);
|
||||||
|
if (existingOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
|
Caliber c = existingOpt.get();
|
||||||
|
if (c.getDeletedAt() != null) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
|
c.setIsActive(active);
|
||||||
|
c.setUpdatedAt(OffsetDateTime.now());
|
||||||
|
|
||||||
|
Caliber saved = caliberRepository.save(c);
|
||||||
|
return ResponseEntity.ok(toDto(saved));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> delete(@PathVariable Integer id) {
|
||||||
|
var existingOpt = caliberRepository.findById(id);
|
||||||
|
if (existingOpt.isEmpty()) return ResponseEntity.notFound().build();
|
||||||
|
|
||||||
|
Caliber c = existingOpt.get();
|
||||||
|
if (c.getDeletedAt() != null) return ResponseEntity.noContent().build();
|
||||||
|
|
||||||
|
c.setDeletedAt(OffsetDateTime.now());
|
||||||
|
c.setUpdatedAt(OffsetDateTime.now());
|
||||||
|
caliberRepository.save(c);
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeKey(String raw) {
|
||||||
|
return raw == null ? null : raw.trim().toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CaliberDto toDto(Caliber c) {
|
||||||
|
return new CaliberDto(
|
||||||
|
c.getId(),
|
||||||
|
c.getKey(),
|
||||||
|
c.getLabel(),
|
||||||
|
c.getIsActive(),
|
||||||
|
c.getCreatedAt(),
|
||||||
|
c.getUpdatedAt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// web/dto/admin/CaliberDto.java
|
||||||
|
package group.goforward.battlbuilder.web.dto.admin;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
|
||||||
|
public record CaliberDto(
|
||||||
|
Integer id,
|
||||||
|
String key,
|
||||||
|
String label,
|
||||||
|
Boolean isActive,
|
||||||
|
OffsetDateTime createdAt,
|
||||||
|
OffsetDateTime updatedAt
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// web/dto/admin/CaliberUpsertRequest.java
|
||||||
|
package group.goforward.battlbuilder.web.dto.admin;
|
||||||
|
|
||||||
|
public record CaliberUpsertRequest(
|
||||||
|
String key,
|
||||||
|
String label,
|
||||||
|
Boolean isActive
|
||||||
|
) {}
|
||||||
@@ -5,17 +5,13 @@ spring.datasource.username=postgres
|
|||||||
spring.datasource.password=cul8rman
|
spring.datasource.password=cul8rman
|
||||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||||
|
|
||||||
# Hibernate properties
|
|
||||||
#spring.jpa.hibernate.ddl-auto=update
|
|
||||||
spring.jpa.show-sql=true
|
|
||||||
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
|
||||||
|
|
||||||
security.jwt.secret=ballistic-test-secret-key-1234567890-ABCDEFGHIJKLNMOPQRST
|
security.jwt.secret=ballistic-test-secret-key-1234567890-ABCDEFGHIJKLNMOPQRST
|
||||||
security.jwt.access-token-minutes=2880
|
security.jwt.access-token-minutes=2880
|
||||||
|
|
||||||
|
|
||||||
# Hibernate properties
|
# Hibernate properties
|
||||||
|
|
||||||
#spring.jpa.hibernate.ddl-auto=update
|
#spring.jpa.hibernate.ddl-auto=update
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||||
@@ -23,6 +19,14 @@ spring.jpa.show-sql=true
|
|||||||
logging.level.org.hibernate.SQL=off
|
logging.level.org.hibernate.SQL=off
|
||||||
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=warn
|
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=warn
|
||||||
logging.level.org.hibernate.orm.jdbc.bind=OFF
|
logging.level.org.hibernate.orm.jdbc.bind=OFF
|
||||||
|
logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=DEBUG
|
||||||
|
|
||||||
|
# expose actuator endpoints you want
|
||||||
|
management.endpoints.web.exposure.include=health,info,mappings
|
||||||
|
|
||||||
|
# (optional) nicer output + shows details
|
||||||
|
management.endpoint.mappings.enabled=true
|
||||||
|
management.endpoint.health.show-details=always
|
||||||
|
|
||||||
# JSP Configuration
|
# JSP Configuration
|
||||||
spring.mvc.view.prefix=/WEB-INF/views/
|
spring.mvc.view.prefix=/WEB-INF/views/
|
||||||
|
|||||||
Reference in New Issue
Block a user