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.util.List;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/platforms")
|
||||
@@ -102,4 +103,35 @@ public class AdminPlatformController {
|
||||
public ResponseEntity<Void> deleteLegacy(@PathVariable Integer 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;
|
||||
|
||||
import group.goforward.battlbuilder.model.Caliber;
|
||||
import group.goforward.battlbuilder.model.Product;
|
||||
import group.goforward.battlbuilder.model.enums.PartRoleSource;
|
||||
import group.goforward.battlbuilder.repos.CaliberRepository;
|
||||
import group.goforward.battlbuilder.repos.ProductRepository;
|
||||
import group.goforward.battlbuilder.services.admin.AdminProductService;
|
||||
import group.goforward.battlbuilder.domain.specs.ProductSpecifications;
|
||||
@@ -36,9 +38,11 @@ import java.time.Instant;
|
||||
public class AdminProductServiceImpl implements AdminProductService {
|
||||
|
||||
private final ProductRepository productRepository;
|
||||
private final CaliberRepository caliberRepository;
|
||||
|
||||
public AdminProductServiceImpl(ProductRepository productRepository) {
|
||||
public AdminProductServiceImpl(ProductRepository productRepository, CaliberRepository caliberRepository) {
|
||||
this.productRepository = productRepository;
|
||||
this.caliberRepository = caliberRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -241,6 +245,17 @@ public class AdminProductServiceImpl implements AdminProductService {
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
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);
|
||||
|
||||
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.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.access-token-minutes=2880
|
||||
|
||||
|
||||
# Hibernate properties
|
||||
|
||||
#spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
#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.type.descriptor.sql.BasicBinder=warn
|
||||
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
|
||||
spring.mvc.view.prefix=/WEB-INF/views/
|
||||
|
||||
Reference in New Issue
Block a user