changes to build so we can create the screen that is missing

This commit is contained in:
Don Strawsburg
2026-01-14 16:19:50 -05:00
parent 52b9ffd105
commit 94be32fed0
13 changed files with 327 additions and 52 deletions

View File

@@ -2,6 +2,7 @@ package group.goforward.battlbuilder.controllers.api.v1;
import group.goforward.battlbuilder.model.Build;
import group.goforward.battlbuilder.repos.build.BuildRepository;
import group.goforward.battlbuilder.web.dto.build.BuildDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -10,7 +11,7 @@ import java.util.List;
@RestController
@RequestMapping("/v1/api/builds")
@RequestMapping("/api/builds")
public class BuildController {
@Autowired
private BuildRepository repo;

View File

@@ -0,0 +1,2 @@
package group.goforward.battlbuilder.domain;
;

View File

@@ -0,0 +1,11 @@
/**
* Imports of data from feeds
*
* <p>The main entry point for managing the inventory is the
* {@link group.goforward.battlbuilder.BattlBuilderApplication} class.</p>
*
* @since 1.0
* @author Sean Strawsburg
* @version 1.1
*/
package group.goforward.battlbuilder.imports;

View File

@@ -0,0 +1,99 @@
package group.goforward.battlbuilder.mapper;
import group.goforward.battlbuilder.model.BuildItem;
import group.goforward.battlbuilder.model.Product;
import group.goforward.battlbuilder.web.dto.build.BuildItemDto;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public final class BuildItemMapper {
private BuildItemMapper() {
// utility class
}
// ---------------------------------------------------------
// BuildItem → BuildItemDto
// ---------------------------------------------------------
public static BuildItemDto toDto(BuildItem item) {
if (item == null) {
return null;
}
BuildItemDto dto = new BuildItemDto();
dto.setId(item.getId() != null ? String.valueOf(item.getId()) : null);
dto.setUuid(item.getUuid());
dto.setSlot(item.getSlot());
dto.setPosition(item.getPosition());
dto.setQuantity(item.getQuantity());
Product product = item.getProduct();
if (product != null) {
dto.setProductId(product.getId() != null ? String.valueOf(product.getId()) : null);
dto.setProductName(product.getName());
dto.setProductBrand(
product.getBrand() != null ? product.getBrand().getName() : null
);
dto.setProductImageUrl(product.getMainImageUrl());
// bestPrice remains a concern of a pricing service / aggregator
}
return dto;
}
// ---------------------------------------------------------
// BuildItemDto → BuildItem
// ---------------------------------------------------------
public static BuildItem toEntity(BuildItemDto dto) {
if (dto == null) {
return null;
}
BuildItem entity = new BuildItem();
if (dto.getId() != null && !dto.getId().isBlank()) {
try {
entity.setId(Integer.valueOf(dto.getId()));
} catch (NumberFormatException ignored) {
// leave id null if parsing fails
}
}
entity.setUuid(dto.getUuid());
entity.setSlot(dto.getSlot());
entity.setPosition(dto.getPosition());
entity.setQuantity(dto.getQuantity());
// Product + Build references should be set by the service layer:
// - resolve product via ProductRepository using dto.getProductId()
// - assign Build via build.addItem(item) or item.setBuild(build)
return entity;
}
// ---------------------------------------------------------
// Collection helpers
// ---------------------------------------------------------
public static List<BuildItemDto> toDtoList(List<BuildItem> items) {
if (items == null) {
return null;
}
return items.stream()
.filter(Objects::nonNull)
.map(BuildItemMapper::toDto)
.collect(Collectors.toList());
}
public static List<BuildItem> toEntityList(List<BuildItemDto> dtos) {
if (dtos == null) {
return null;
}
return dtos.stream()
.filter(Objects::nonNull)
.map(BuildItemMapper::toEntity)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,123 @@
package group.goforward.battlbuilder.mapper;
import group.goforward.battlbuilder.model.Build;
import group.goforward.battlbuilder.model.BuildItem;
import group.goforward.battlbuilder.model.Product;
import group.goforward.battlbuilder.web.dto.build.BuildDto;
import group.goforward.battlbuilder.web.dto.build.BuildItemDto;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public final class BuildMapper {
private BuildMapper() {
// utility class
}
// ---------------------------------------------------------
// Build → BuildDto
// ---------------------------------------------------------
public static BuildDto toDto(Build entity) {
if (entity == null) {
return null;
}
BuildDto dto = new BuildDto();
dto.setId(entity.getId() != null ? String.valueOf(entity.getId()) : null);
dto.setUuid(entity.getUuid());
dto.setTitle(entity.getTitle());
dto.setDescription(entity.getDescription());
dto.setIsPublic(entity.getIsPublic());
dto.setCreatedAt(entity.getCreatedAt());
dto.setUpdatedAt(entity.getUpdatedAt());
if (entity.getItems() != null) {
dto.setItems(
entity.getItems().stream()
.filter(Objects::nonNull)
.map(BuildMapper::toItemDto)
.collect(Collectors.toList())
);
}
return dto;
}
// ---------------------------------------------------------
// BuildDto → Build
// ---------------------------------------------------------
public static Build toEntity(BuildDto dto) {
if (dto == null) {
return null;
}
Build entity = new Build();
if (dto.getId() != null && !dto.getId().isBlank()) {
try {
entity.setId(Integer.valueOf(dto.getId()));
} catch (NumberFormatException ignored) {
// leave id null if it can't be parsed
}
}
entity.setUuid(dto.getUuid());
entity.setTitle(dto.getTitle());
entity.setDescription(dto.getDescription());
entity.setIsPublic(dto.getIsPublic());
entity.setCreatedAt(dto.getCreatedAt());
entity.setUpdatedAt(dto.getUpdatedAt());
// Items are typically managed separately (service layer),
// so we don't automatically map DTO items back to entities here.
// If you want that, wire in a factory/lookup for Product and hydrate BuildItem.
return entity;
}
// ---------------------------------------------------------
// BuildItem → BuildItemDto
// ---------------------------------------------------------
private static BuildItemDto toItemDto(BuildItem item) {
BuildItemDto dto = new BuildItemDto();
dto.setId(item.getId() != null ? String.valueOf(item.getId()) : null);
dto.setUuid(item.getUuid());
dto.setSlot(item.getSlot());
dto.setPosition(item.getPosition());
dto.setQuantity(item.getQuantity());
Product product = item.getProduct();
if (product != null) {
dto.setProductId(product.getId() != null ? String.valueOf(product.getId()) : null);
dto.setProductName(product.getName());
dto.setProductBrand(
product.getBrand() != null ? product.getBrand().getName() : null
);
dto.setProductImageUrl(product.getMainImageUrl());
// bestPrice is intentionally left for service-layer aggregation
}
return dto;
}
// ---------------------------------------------------------
// Collection helpers
// ---------------------------------------------------------
public static List<BuildDto> toDtoList(List<Build> entities) {
if (entities == null) {
return null;
}
return entities.stream()
.filter(Objects::nonNull)
.map(BuildMapper::toDto)
.collect(Collectors.toList());
}
}

View File

@@ -1,10 +1,13 @@
package group.goforward.battlbuilder.model;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
@@ -60,6 +63,10 @@ public class Build {
@Column(name = "deleted_at")
private OffsetDateTime deletedAt;
@JsonManagedReference
@OneToMany(mappedBy = "build", cascade = CascadeType.ALL, orphanRemoval = true)
private List<BuildItem> items = new ArrayList<>();
// -----------------------------------------------------
// Hibernate lifecycle
// -----------------------------------------------------
@@ -106,4 +113,22 @@ public class Build {
public OffsetDateTime getDeletedAt() { return deletedAt; }
public void setDeletedAt(OffsetDateTime deletedAt) { this.deletedAt = deletedAt; }
public List<BuildItem> getItems() {
return items;
}
public void setItems(List<BuildItem> items) {
this.items = items;
}
public void addItem(BuildItem item) {
items.add(item);
item.setBuild(this);
}
public void removeItem(BuildItem item) {
items.remove(item);
item.setBuild(null);
}
}

View File

@@ -1,5 +1,6 @@
package group.goforward.battlbuilder.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import org.hibernate.annotations.ColumnDefault;
@@ -22,6 +23,7 @@ public class BuildItem {
@Column(name = "uuid", nullable = false)
private UUID uuid;
@JsonBackReference
@NotNull
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)

View File

@@ -41,7 +41,7 @@ public class Product {
private UUID uuid;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "brand_id", nullable = false)
@JoinColumn(name = "brand_id", nullable = true)
private Brand brand;
@Column(name = "name", nullable = false)

View File

@@ -79,3 +79,6 @@ app.beta.invite.tokenMinutes=30
ai.minConfidence=0.75
ai.openai.apiKey=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
ai.openai.model=gpt-4.1-mini
spring.jackson.serialization.fail-on-empty-beans=false