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

88
.idea/dataSources.xml generated
View File

@@ -1,31 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="r710" uuid="e6a29f5c-71d9-45f0-931b-554bcf8a94ba">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://r710.dev.gofwd.group:5433/postgres</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="ss_builder@r710.gofwd.group" uuid="e0fa459b-2f6c-45f1-9c41-66423c870df9">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/src/main/resources/application.properties</remarks>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://r710.gofwd.group:5433/ss_builder</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="ss_builder@r710.dev.gofwd.group" uuid="f0642c9f-ddd8-4def-ab05-e99fc501ff79">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/src/main/resources/application.properties</remarks>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://r710.dev.gofwd.group:5433/ss_builder</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="ss_builder@r710.dev.gofwd.group [2]" uuid="d2bd5425-01da-4cf0-8755-229e119dac05">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/src/main/resources/application.properties</remarks>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://r710.dev.gofwd.group:5433/ss_builder</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="r710" uuid="14176613-3667-4490-82a9-caaa686ecb9c">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://r710.dev.gofwd.group:5433/postgres</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="ss_builder@r710.gofwd.group" uuid="9ecc288e-c6f1-4444-85ce-ecad1cb0c4df">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<imported>true</imported>
<remarks>$PROJECT_DIR$/src/main/resources/application.properties</remarks>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://r710.gofwd.group:5433/ss_builder</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -1,19 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

View File

@@ -44,7 +44,7 @@ The frontend Builder depends on this backend for:
## Tech Stack
- **Spring Boot 3.x**
- **Java 17**
- **Java 21**
- **PostgreSQL**
- **Hibernate (JPA)**
- **HikariCP**

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

View File