mirror of
https://gitea.gofwd.group/Forward_Group/ballistic-builder-spring.git
synced 2026-01-20 16:51:03 -05:00
support for user builds and user accounts
This commit is contained in:
@@ -1,18 +1,22 @@
|
||||
package group.goforward.battlbuilder.controllers;
|
||||
|
||||
import group.goforward.battlbuilder.model.Build;
|
||||
import group.goforward.battlbuilder.model.BuildItem;
|
||||
import group.goforward.battlbuilder.model.Product;
|
||||
import group.goforward.battlbuilder.model.ProductOffer;
|
||||
import group.goforward.battlbuilder.repos.BuildItemRepository;
|
||||
import group.goforward.battlbuilder.repos.BuildRepository;
|
||||
import group.goforward.battlbuilder.repos.ProductOfferRepository;
|
||||
import group.goforward.battlbuilder.repos.ProductRepository;
|
||||
import group.goforward.battlbuilder.web.dto.BuildCreateRequest;
|
||||
import group.goforward.battlbuilder.web.dto.BuildDto;
|
||||
import group.goforward.battlbuilder.web.dto.BuildItemDto;
|
||||
import group.goforward.battlbuilder.web.dto.ProductDto;
|
||||
import group.goforward.battlbuilder.web.dto.ProductOfferDto;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -24,14 +28,26 @@ public class ProductV1Controller {
|
||||
private final ProductRepository productRepository;
|
||||
private final ProductOfferRepository productOfferRepository;
|
||||
|
||||
// ✅ Builds
|
||||
private final BuildRepository buildRepository;
|
||||
private final BuildItemRepository buildItemRepository;
|
||||
|
||||
public ProductV1Controller(
|
||||
ProductRepository productRepository,
|
||||
ProductOfferRepository productOfferRepository
|
||||
ProductOfferRepository productOfferRepository,
|
||||
BuildRepository buildRepository,
|
||||
BuildItemRepository buildItemRepository
|
||||
) {
|
||||
this.productRepository = productRepository;
|
||||
this.productOfferRepository = productOfferRepository;
|
||||
this.buildRepository = buildRepository;
|
||||
this.buildItemRepository = buildItemRepository;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PRODUCTS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* List products (v1 summary contract)
|
||||
* Keep this lightweight for grids/lists.
|
||||
@@ -98,7 +114,7 @@ public class ProductV1Controller {
|
||||
|
||||
Product product = productOpt.get();
|
||||
|
||||
// Pull offers + merchant (your Hibernate log shows this join is happening)
|
||||
// Pull offers + merchant
|
||||
List<ProductOffer> offers = productOfferRepository.findByProductId(productId);
|
||||
ProductOffer best = pickBestOffer(offers);
|
||||
|
||||
@@ -106,228 +122,208 @@ public class ProductV1Controller {
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Helpers
|
||||
// ---------------------------
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Description normalization (feed -> readable bullets)
|
||||
// ---------------------------------------------------------------------
|
||||
// =========================================================================
|
||||
// ME BUILDS (TEMP HOME INSIDE ProductV1Controller)
|
||||
// NOTE: Routes become /api/v1/products/me/builds (works now, we can refactor later)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Normalize ugly feed descriptions into readable, bullet-point friendly text.
|
||||
* MAIN FUNCTIONALITY:
|
||||
* Create/save a build for the current logged-in user.
|
||||
*
|
||||
* Goals:
|
||||
* - Break common section headers onto their own lines (Features, Specs, Includes, etc.)
|
||||
* - Convert "glued" headers like "FeaturesSub-MOA..." into "Features\n- Sub-MOA..."
|
||||
* - Split run-on strings into bullets using separators (; | • \n) and heuristic sentence breaks
|
||||
* - Keep it safe (no HTML parsing, no fancy dependencies)
|
||||
* This expects resolveUserId(auth) to return an Integer userId.
|
||||
*/
|
||||
private static String normalizeDescription(String raw) {
|
||||
if (raw == null) return null;
|
||||
@PostMapping("/me/builds")
|
||||
public ResponseEntity<BuildDto> createBuild(
|
||||
Authentication auth,
|
||||
@RequestBody BuildCreateRequest req
|
||||
) {
|
||||
Integer userId = resolveUserId(auth);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
|
||||
String s = raw.trim();
|
||||
if (s.isEmpty()) return null;
|
||||
|
||||
// Basic cleanup (some feeds stuff literal \u003E etc, but your example is mostly plain text)
|
||||
s = s.replace("\r\n", "\n").replace("\r", "\n");
|
||||
s = s.replace("\t", " ");
|
||||
s = collapseSpaces(s);
|
||||
|
||||
// Fix the classic "FeaturesSub-MOA" glued header issue
|
||||
s = unglueHeaders(s);
|
||||
|
||||
// Add newlines around known headers to create sections
|
||||
s = normalizeSectionHeaders(s);
|
||||
|
||||
// If it already contains line breaks, we'll bullet-ify lines; otherwise split heuristically.
|
||||
s = bulletizeContent(s);
|
||||
|
||||
// Safety trim: keep details readable, not infinite
|
||||
int MAX = 8000;
|
||||
if (s.length() > MAX) {
|
||||
s = s.substring(0, MAX).trim() + "…";
|
||||
if (req == null || req.getTitle() == null || req.getTitle().isBlank()) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
return s.trim();
|
||||
Build b = new Build();
|
||||
b.setUserId(userId);
|
||||
b.setTitle(req.getTitle().trim());
|
||||
b.setDescription(req.getDescription());
|
||||
b.setIsPublic(req.getIsPublic() != null && req.getIsPublic());
|
||||
|
||||
b = buildRepository.save(b);
|
||||
|
||||
// Save items
|
||||
List<BuildCreateRequest.BuildItemCreateRequest> items =
|
||||
req.getItems() != null ? req.getItems() : List.of();
|
||||
|
||||
// Prefetch products in one pass (avoid N+1)
|
||||
Set<Integer> productIds = items.stream()
|
||||
.map(BuildCreateRequest.BuildItemCreateRequest::getProductId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Integer, Product> productsById = productIds.isEmpty()
|
||||
? Map.of()
|
||||
: productRepository.findAllById(productIds).stream()
|
||||
.collect(Collectors.toMap(Product::getId, p -> p));
|
||||
|
||||
for (BuildCreateRequest.BuildItemCreateRequest it : items) {
|
||||
if (it == null) continue;
|
||||
if (it.getProductId() == null) continue;
|
||||
if (it.getSlot() == null || it.getSlot().isBlank()) continue;
|
||||
|
||||
Product p = productsById.get(it.getProductId());
|
||||
if (p == null) continue;
|
||||
|
||||
BuildItem bi = new BuildItem();
|
||||
bi.setBuild(b);
|
||||
bi.setProduct(p);
|
||||
bi.setSlot(it.getSlot().trim());
|
||||
bi.setPosition(it.getPosition() != null ? it.getPosition() : 0);
|
||||
bi.setQuantity(it.getQuantity() != null ? it.getQuantity() : 1);
|
||||
|
||||
buildItemRepository.save(bi);
|
||||
}
|
||||
|
||||
List<BuildItem> savedItems = buildItemRepository.findByBuild_Id(b.getId());
|
||||
return ResponseEntity.ok(toBuildDto(b, savedItems));
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a short description from the normalized description.
|
||||
* Keeps the page from looking empty when shortDescription is null in feeds.
|
||||
* MAIN FUNCTIONALITY:
|
||||
* List builds for the current user (lightweight list; no items).
|
||||
*
|
||||
* NOTE: For performance, you should add a real repo method later like:
|
||||
* findByUserIdAndDeletedAtIsNullOrderByUpdatedAtDesc(userId)
|
||||
*/
|
||||
private static String deriveShortDescription(String normalizedDescription) {
|
||||
if (normalizedDescription == null || normalizedDescription.isBlank()) return null;
|
||||
@GetMapping("/me/builds")
|
||||
public ResponseEntity<List<BuildDto>> listMyBuilds(Authentication auth) {
|
||||
Integer userId = resolveUserId(auth);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
|
||||
// Use first non-empty line that isn't a header like "Features"
|
||||
String[] lines = normalizedDescription.split("\n");
|
||||
for (String line : lines) {
|
||||
String t = line.trim();
|
||||
if (t.isEmpty()) continue;
|
||||
if (isHeaderLine(t)) continue;
|
||||
List<Build> mine = buildRepository.findByUserIdAndDeletedAtIsNullOrderByUpdatedAtDesc(userId);
|
||||
return ResponseEntity.ok(mine.stream().map(this::toBuildDtoNoItems).toList());
|
||||
}
|
||||
|
||||
// If it's a bullet line, strip leading "- "
|
||||
if (t.startsWith("- ")) t = t.substring(2).trim();
|
||||
|
||||
// Keep it short
|
||||
int MAX = 220;
|
||||
if (t.length() > MAX) t = t.substring(0, MAX).trim() + "…";
|
||||
return t;
|
||||
@PutMapping("/me/builds/{uuid}")
|
||||
public ResponseEntity<BuildDto> replaceBuildItems(
|
||||
Authentication auth,
|
||||
@PathVariable("uuid") String uuidRaw,
|
||||
@RequestBody BuildCreateRequest req
|
||||
) {
|
||||
Integer userId = resolveUserId(auth);
|
||||
if (userId == null) return ResponseEntity.status(401).build();
|
||||
|
||||
UUID uuid;
|
||||
try {
|
||||
uuid = UUID.fromString(uuidRaw);
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Optional<Build> opt = buildRepository.findByUuid(uuid);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
|
||||
private static boolean isHeaderLine(String line) {
|
||||
String l = line.toLowerCase(Locale.ROOT).trim();
|
||||
return l.equals("features") ||
|
||||
l.equals("specifications") ||
|
||||
l.equals("specs") ||
|
||||
l.equals("includes") ||
|
||||
l.equals("notes") ||
|
||||
l.equals("overview");
|
||||
}
|
||||
Build b = opt.get();
|
||||
if (b.getDeletedAt() != null) return ResponseEntity.notFound().build();
|
||||
|
||||
private static String collapseSpaces(String s) {
|
||||
// Collapse repeating spaces (but keep newlines)
|
||||
return s.replaceAll("[ ]{2,}", " ");
|
||||
// ✅ Owner check
|
||||
if (!Objects.equals(b.getUserId(), userId)) {
|
||||
return ResponseEntity.status(403).build();
|
||||
}
|
||||
|
||||
// Optional: allow updating build metadata too
|
||||
if (req != null) {
|
||||
if (req.getTitle() != null && !req.getTitle().isBlank()) {
|
||||
b.setTitle(req.getTitle().trim());
|
||||
}
|
||||
b.setDescription(req.getDescription());
|
||||
if (req.getIsPublic() != null) b.setIsPublic(req.getIsPublic());
|
||||
b = buildRepository.save(b);
|
||||
}
|
||||
|
||||
// ✅ Replace items: delete then insert
|
||||
buildItemRepository.deleteByBuild_Id(b.getId());
|
||||
|
||||
List<BuildCreateRequest.BuildItemCreateRequest> items =
|
||||
(req != null && req.getItems() != null) ? req.getItems() : List.of();
|
||||
|
||||
Set<Integer> productIds = items.stream()
|
||||
.map(BuildCreateRequest.BuildItemCreateRequest::getProductId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Map<Integer, Product> productsById = productIds.isEmpty()
|
||||
? Map.of()
|
||||
: productRepository.findAllById(productIds).stream()
|
||||
.collect(Collectors.toMap(Product::getId, p -> p));
|
||||
|
||||
for (BuildCreateRequest.BuildItemCreateRequest it : items) {
|
||||
if (it == null) continue;
|
||||
if (it.getProductId() == null) continue;
|
||||
if (it.getSlot() == null || it.getSlot().isBlank()) continue;
|
||||
|
||||
Product p = productsById.get(it.getProductId());
|
||||
if (p == null) continue;
|
||||
|
||||
BuildItem bi = new BuildItem();
|
||||
bi.setBuild(b);
|
||||
bi.setProduct(p);
|
||||
bi.setSlot(it.getSlot().trim());
|
||||
bi.setPosition(it.getPosition() != null ? it.getPosition() : 0);
|
||||
bi.setQuantity(it.getQuantity() != null ? it.getQuantity() : 1);
|
||||
|
||||
// IMPORTANT if your BuildItem doesn't have @PrePersist yet:
|
||||
// bi.setCreatedAt(OffsetDateTime.now());
|
||||
|
||||
buildItemRepository.save(bi);
|
||||
}
|
||||
|
||||
List<BuildItem> savedItems = buildItemRepository.findByBuild_Id(b.getId());
|
||||
return ResponseEntity.ok(toBuildDto(b, savedItems));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert spaces/newlines when section headers are glued to the next word.
|
||||
* Example: "FeaturesSub-MOA Accuracy" -> "Features\nSub-MOA Accuracy"
|
||||
* MAIN FUNCTIONALITY:
|
||||
* Load build by UUID.
|
||||
* - Owner can see it
|
||||
* - Non-owner can only see if is_public = true
|
||||
*/
|
||||
private static String unglueHeaders(String s) {
|
||||
// Known headers that show up glued in feeds
|
||||
String[] headers = new String[] {
|
||||
"Features", "Specifications", "Specs", "Includes", "Overview", "Notes"
|
||||
};
|
||||
|
||||
for (String h : headers) {
|
||||
// Header followed immediately by a letter/number (no space) => add newline
|
||||
s = s.replaceAll("(?i)\\b" + h + "(?=[A-Za-z0-9])", h + "\n");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put headers on their own line and add a blank line before them (except at start).
|
||||
*/
|
||||
private static String normalizeSectionHeaders(String s) {
|
||||
// Make sure headers start on a new line if they appear mid-string
|
||||
s = s.replaceAll("(?i)\\s*(\\bFeatures\\b)\\s*", "\n\nFeatures\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bSpecifications\\b|\\bSpecs\\b)\\s*", "\n\nSpecifications\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bIncludes\\b)\\s*", "\n\nIncludes\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bOverview\\b)\\s*", "\n\nOverview\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bNotes\\b)\\s*", "\n\nNotes\n");
|
||||
|
||||
// Clean extra blank lines
|
||||
s = s.replaceAll("\\n{3,}", "\n\n");
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert content lines into bullets where it makes sense.
|
||||
* - Splits on common separators (; | •) into bullet lines
|
||||
* - For long single-line descriptions, splits into pseudo-bullets by sentence-ish breaks
|
||||
*/
|
||||
private static String bulletizeContent(String s) {
|
||||
String[] lines = s.split("\n");
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
for (String line : lines) {
|
||||
String t = line.trim();
|
||||
if (t.isEmpty()) {
|
||||
out.append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Preserve headers as-is
|
||||
if (isHeaderLine(t)) {
|
||||
out.append(t).append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// If line already looks like bullets, keep it
|
||||
if (t.startsWith("- ") || t.startsWith("• ")) {
|
||||
out.append(t.startsWith("• ") ? "- " + t.substring(2).trim() : t).append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split on common separators first
|
||||
List<String> parts = splitOnSeparators(t);
|
||||
|
||||
if (parts.size() == 1) {
|
||||
// Heuristic: split long run-on text into "sentences"
|
||||
parts = splitHeuristic(parts.get(0));
|
||||
}
|
||||
|
||||
// Bullet the parts
|
||||
if (parts.size() > 1) {
|
||||
for (String p : parts) {
|
||||
String bp = p.trim();
|
||||
if (bp.isEmpty()) continue;
|
||||
out.append("- ").append(bp).append("\n");
|
||||
}
|
||||
} else {
|
||||
// Single line: keep as paragraph (no bullet)
|
||||
out.append(t).append("\n");
|
||||
}
|
||||
@GetMapping("/builds/{uuid}")
|
||||
public ResponseEntity<BuildDto> getBuildByUuid(
|
||||
Authentication auth,
|
||||
@PathVariable("uuid") String uuidRaw
|
||||
) {
|
||||
UUID uuid;
|
||||
try {
|
||||
uuid = UUID.fromString(uuidRaw);
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
// Cleanup: trim extra blank lines
|
||||
String result = out.toString().replaceAll("\\n{3,}", "\n\n").trim();
|
||||
return result;
|
||||
Optional<Build> opt = buildRepository.findByUuid(uuid);
|
||||
if (opt.isEmpty()) return ResponseEntity.notFound().build();
|
||||
|
||||
Build b = opt.get();
|
||||
if (b.getDeletedAt() != null) return ResponseEntity.notFound().build();
|
||||
|
||||
Integer userId = resolveUserId(auth);
|
||||
boolean isOwner = userId != null && Objects.equals(b.getUserId(), userId);
|
||||
boolean isPublic = Boolean.TRUE.equals(b.getIsPublic());
|
||||
|
||||
if (!isOwner && !isPublic) return ResponseEntity.status(403).build();
|
||||
|
||||
List<BuildItem> items = buildItemRepository.findByBuild_Uuid(uuid);
|
||||
return ResponseEntity.ok(toBuildDto(b, items));
|
||||
}
|
||||
|
||||
private static List<String> splitOnSeparators(String line) {
|
||||
// Split on ; | • (common feed separators)
|
||||
// Keep it conservative: don't split on commas (would explode calibers etc.)
|
||||
String normalized = line.replace("•", "|");
|
||||
String[] raw = normalized.split("\\s*[;|]\\s*");
|
||||
List<String> parts = new ArrayList<>();
|
||||
for (String r : raw) {
|
||||
String t = r.trim();
|
||||
if (!t.isEmpty()) parts.add(t);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private static List<String> splitHeuristic(String line) {
|
||||
String t = line.trim();
|
||||
if (t.length() < 180) return List.of(t); // short line = keep as paragraph
|
||||
|
||||
// Split on ". " and " - " only when it looks like sentences, not decimals (e.g. 17.5)
|
||||
// This is a heuristic, not perfect.
|
||||
List<String> parts = new ArrayList<>();
|
||||
String[] chunks = t.split("(?<!\\d)\\.\\s+"); // avoid splitting 17.5
|
||||
for (String c : chunks) {
|
||||
String s = c.trim();
|
||||
if (s.isEmpty()) continue;
|
||||
// Further split on " - " if it seems list-like
|
||||
if (s.contains(" - ")) {
|
||||
for (String sub : s.split("\\s+-\\s+")) {
|
||||
String ss = sub.trim();
|
||||
if (!ss.isEmpty()) parts.add(ss);
|
||||
}
|
||||
} else {
|
||||
parts.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
// If heuristic didn't help, return original
|
||||
if (parts.size() <= 1) return List.of(t);
|
||||
|
||||
// Cap bullet count so we don't spam 50 bullets
|
||||
int MAX_BULLETS = 18;
|
||||
if (parts.size() > MAX_BULLETS) {
|
||||
parts = parts.subList(0, MAX_BULLETS);
|
||||
parts.add("…");
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
// =========================================================================
|
||||
// HELPERS — Products
|
||||
// =========================================================================
|
||||
|
||||
private static Integer parsePositiveInt(String raw) {
|
||||
try {
|
||||
@@ -368,9 +364,7 @@ public class ProductV1Controller {
|
||||
dto.setBuyUrl(bestOffer != null ? bestOffer.getBuyUrl() : null);
|
||||
dto.setInStock(bestOffer != null ? bestOffer.getInStock() : null);
|
||||
|
||||
// v1 list expects imageUrl
|
||||
dto.setImageUrl(p.getMainImageUrl());
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -380,7 +374,6 @@ public class ProductV1Controller {
|
||||
private ProductDto toProductDtoDetails(Product p, ProductOffer bestOffer, List<ProductOffer> offers) {
|
||||
ProductDto dto = new ProductDto();
|
||||
|
||||
// --- core identity ---
|
||||
dto.setId(String.valueOf(p.getId()));
|
||||
dto.setName(p.getName());
|
||||
dto.setBrand(p.getBrand() != null ? p.getBrand().getName() : null);
|
||||
@@ -388,24 +381,20 @@ public class ProductV1Controller {
|
||||
dto.setPartRole(p.getPartRole());
|
||||
dto.setCategoryKey(p.getRawCategoryKey());
|
||||
|
||||
// --- best offer summary (used for headline price + CTA fallback) ---
|
||||
dto.setPrice(bestOffer != null ? bestOffer.getEffectivePrice() : null);
|
||||
dto.setBuyUrl(bestOffer != null ? bestOffer.getBuyUrl() : null);
|
||||
dto.setInStock(bestOffer != null ? bestOffer.getInStock() : null);
|
||||
|
||||
// --- images ---
|
||||
dto.setImageUrl(p.getMainImageUrl()); // legacy field UI already uses
|
||||
dto.setImageUrl(p.getMainImageUrl());
|
||||
dto.setMainImageUrl(p.getMainImageUrl());
|
||||
dto.setBattlImageUrl(p.getBattlImageUrl());
|
||||
|
||||
// --- richer product fields ---
|
||||
dto.setSlug(p.getSlug());
|
||||
dto.setMpn(p.getMpn());
|
||||
dto.setUpc(p.getUpc());
|
||||
dto.setConfiguration(p.getConfiguration() != null ? p.getConfiguration().name() : null);
|
||||
dto.setPlatformLocked(p.getPlatformLocked());
|
||||
|
||||
// --- description normalizer/formatter
|
||||
String normalized = normalizeDescription(p.getDescription());
|
||||
dto.setDescription(normalized);
|
||||
|
||||
@@ -415,28 +404,20 @@ public class ProductV1Controller {
|
||||
}
|
||||
dto.setShortDescription(shortDesc);
|
||||
|
||||
// --- offers table ---
|
||||
List<ProductOfferDto> offerDtos = (offers == null ? List.<ProductOffer>of() : offers)
|
||||
.stream()
|
||||
.map(this::toOfferDto)
|
||||
.toList();
|
||||
|
||||
dto.setOffers(offerDtos);
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer -> ProductOfferDto mapper
|
||||
* (This is what powers your offers table in the UI)
|
||||
*/
|
||||
private ProductOfferDto toOfferDto(ProductOffer o) {
|
||||
ProductOfferDto dto = new ProductOfferDto();
|
||||
|
||||
dto.setId(String.valueOf(o.getId()));
|
||||
dto.setMerchantName(
|
||||
o.getMerchant() != null ? o.getMerchant().getName() : null
|
||||
);
|
||||
dto.setMerchantName(o.getMerchant() != null ? o.getMerchant().getName() : null);
|
||||
dto.setPrice(o.getPrice());
|
||||
dto.setOriginalPrice(o.getOriginalPrice());
|
||||
dto.setInStock(Boolean.TRUE.equals(o.getInStock()));
|
||||
@@ -447,4 +428,276 @@ public class ProductV1Controller {
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// HELPERS — Description normalization (feed -> readable bullets)
|
||||
// =========================================================================
|
||||
|
||||
private static String normalizeDescription(String raw) {
|
||||
if (raw == null) return null;
|
||||
|
||||
String s = raw.trim();
|
||||
if (s.isEmpty()) return null;
|
||||
|
||||
s = s.replace("\r\n", "\n").replace("\r", "\n");
|
||||
s = s.replace("\t", " ");
|
||||
s = collapseSpaces(s);
|
||||
|
||||
s = unglueHeaders(s);
|
||||
s = normalizeSectionHeaders(s);
|
||||
s = bulletizeContent(s);
|
||||
|
||||
int MAX = 8000;
|
||||
if (s.length() > MAX) {
|
||||
s = s.substring(0, MAX).trim() + "…";
|
||||
}
|
||||
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
private static String deriveShortDescription(String normalizedDescription) {
|
||||
if (normalizedDescription == null || normalizedDescription.isBlank()) return null;
|
||||
|
||||
String[] lines = normalizedDescription.split("\n");
|
||||
for (String line : lines) {
|
||||
String t = line.trim();
|
||||
if (t.isEmpty()) continue;
|
||||
if (isHeaderLine(t)) continue;
|
||||
|
||||
if (t.startsWith("- ")) t = t.substring(2).trim();
|
||||
|
||||
int MAX = 220;
|
||||
if (t.length() > MAX) t = t.substring(0, MAX).trim() + "…";
|
||||
return t;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isHeaderLine(String line) {
|
||||
String l = line.toLowerCase(Locale.ROOT).trim();
|
||||
return l.equals("features")
|
||||
|| l.equals("specifications")
|
||||
|| l.equals("specs")
|
||||
|| l.equals("includes")
|
||||
|| l.equals("notes")
|
||||
|| l.equals("overview");
|
||||
}
|
||||
|
||||
private static String collapseSpaces(String s) {
|
||||
return s.replaceAll("[ ]{2,}", " ");
|
||||
}
|
||||
|
||||
private static String unglueHeaders(String s) {
|
||||
String[] headers = new String[]{"Features", "Specifications", "Specs", "Includes", "Overview", "Notes"};
|
||||
for (String h : headers) {
|
||||
s = s.replaceAll("(?i)\\b" + h + "(?=[A-Za-z0-9])", h + "\n");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private static String normalizeSectionHeaders(String s) {
|
||||
s = s.replaceAll("(?i)\\s*(\\bFeatures\\b)\\s*", "\n\nFeatures\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bSpecifications\\b|\\bSpecs\\b)\\s*", "\n\nSpecifications\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bIncludes\\b)\\s*", "\n\nIncludes\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bOverview\\b)\\s*", "\n\nOverview\n");
|
||||
s = s.replaceAll("(?i)\\s*(\\bNotes\\b)\\s*", "\n\nNotes\n");
|
||||
s = s.replaceAll("\\n{3,}", "\n\n");
|
||||
return s.trim();
|
||||
}
|
||||
|
||||
private static String bulletizeContent(String s) {
|
||||
String[] lines = s.split("\n");
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
for (String line : lines) {
|
||||
String t = line.trim();
|
||||
if (t.isEmpty()) {
|
||||
out.append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isHeaderLine(t)) {
|
||||
out.append(t).append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (t.startsWith("- ") || t.startsWith("• ")) {
|
||||
out.append(t.startsWith("• ") ? "- " + t.substring(2).trim() : t).append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> parts = splitOnSeparators(t);
|
||||
if (parts.size() == 1) {
|
||||
parts = splitHeuristic(parts.get(0));
|
||||
}
|
||||
|
||||
if (parts.size() > 1) {
|
||||
for (String p : parts) {
|
||||
String bp = p.trim();
|
||||
if (bp.isEmpty()) continue;
|
||||
out.append("- ").append(bp).append("\n");
|
||||
}
|
||||
} else {
|
||||
out.append(t).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return out.toString().replaceAll("\\n{3,}", "\n\n").trim();
|
||||
}
|
||||
|
||||
private static List<String> splitOnSeparators(String line) {
|
||||
String normalized = line.replace("•", "|");
|
||||
String[] raw = normalized.split("\\s*[;|]\\s*");
|
||||
List<String> parts = new ArrayList<>();
|
||||
for (String r : raw) {
|
||||
String t = r.trim();
|
||||
if (!t.isEmpty()) parts.add(t);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private static List<String> splitHeuristic(String line) {
|
||||
String t = line.trim();
|
||||
if (t.length() < 180) return List.of(t);
|
||||
|
||||
List<String> parts = new ArrayList<>();
|
||||
String[] chunks = t.split("(?<!\\d)\\.\\s+"); // avoid splitting 17.5
|
||||
for (String c : chunks) {
|
||||
String s = c.trim();
|
||||
if (s.isEmpty()) continue;
|
||||
if (s.contains(" - ")) {
|
||||
for (String sub : s.split("\\s+-\\s+")) {
|
||||
String ss = sub.trim();
|
||||
if (!ss.isEmpty()) parts.add(ss);
|
||||
}
|
||||
} else {
|
||||
parts.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.size() <= 1) return List.of(t);
|
||||
|
||||
int MAX_BULLETS = 18;
|
||||
if (parts.size() > MAX_BULLETS) {
|
||||
parts = parts.subList(0, MAX_BULLETS);
|
||||
parts.add("…");
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// HELPERS — Build DTO mapping + auth user resolution
|
||||
// =========================================================================
|
||||
|
||||
private BuildDto toBuildDtoNoItems(Build b) {
|
||||
BuildDto dto = new BuildDto();
|
||||
dto.setId(String.valueOf(b.getId()));
|
||||
dto.setUuid(b.getUuid());
|
||||
dto.setTitle(b.getTitle());
|
||||
dto.setDescription(b.getDescription());
|
||||
dto.setIsPublic(b.getIsPublic());
|
||||
dto.setCreatedAt(b.getCreatedAt());
|
||||
dto.setUpdatedAt(b.getUpdatedAt());
|
||||
dto.setItems(null);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private BuildDto toBuildDto(Build b, List<BuildItem> items) {
|
||||
BuildDto dto = toBuildDtoNoItems(b);
|
||||
|
||||
List<BuildItemDto> itemDtos = (items == null ? List.<BuildItem>of() : items).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(BuildItem::getSlot)
|
||||
.thenComparing(BuildItem::getPosition))
|
||||
.map(this::toBuildItemDto)
|
||||
.toList();
|
||||
|
||||
dto.setItems(itemDtos);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private BuildItemDto toBuildItemDto(BuildItem bi) {
|
||||
BuildItemDto dto = new BuildItemDto();
|
||||
dto.setId(String.valueOf(bi.getId()));
|
||||
dto.setUuid(bi.getUuid());
|
||||
dto.setSlot(bi.getSlot());
|
||||
dto.setPosition(bi.getPosition());
|
||||
dto.setQuantity(bi.getQuantity());
|
||||
|
||||
Product p = bi.getProduct();
|
||||
if (p != null) {
|
||||
dto.setProductId(String.valueOf(p.getId()));
|
||||
dto.setProductName(p.getName());
|
||||
dto.setProductBrand(p.getBrand() != null ? p.getBrand().getName() : null);
|
||||
dto.setProductImageUrl(p.getBattlImageUrl() != null ? p.getBattlImageUrl() : p.getMainImageUrl());
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* MAIN FUNCTIONALITY (TEMP):
|
||||
* Resolve the logged-in user's id.
|
||||
*
|
||||
* Replace with your real principal logic.
|
||||
*
|
||||
* Common options:
|
||||
* 1) auth.getName() returns email -> lookup userId by email via UserRepository
|
||||
* 2) principal is a custom UserDetails that exposes getId()
|
||||
*/
|
||||
// private Integer resolveUserId(Authentication auth) {
|
||||
// if (auth == null || !auth.isAuthenticated()) return null;
|
||||
//
|
||||
// Object principal = auth.getPrincipal();
|
||||
// if (principal == null) return null;
|
||||
//
|
||||
// // 1) If your principal exposes getId() (custom UserDetails)
|
||||
// try {
|
||||
// var m = principal.getClass().getMethod("getId");
|
||||
// Object id = m.invoke(principal);
|
||||
// if (id instanceof Integer i) return i;
|
||||
// if (id instanceof Long l) return Math.toIntExact(l);
|
||||
// if (id != null) return Integer.parseInt(String.valueOf(id));
|
||||
// } catch (Exception ignored) { }
|
||||
//
|
||||
// // 2) If you store userId on "details"
|
||||
// try {
|
||||
// Object details = auth.getDetails();
|
||||
// if (details != null) {
|
||||
// var m = details.getClass().getMethod("getId");
|
||||
// Object id = m.invoke(details);
|
||||
// if (id instanceof Integer i) return i;
|
||||
// if (id instanceof Long l) return Math.toIntExact(l);
|
||||
// if (id != null) return Integer.parseInt(String.valueOf(id));
|
||||
// }
|
||||
// } catch (Exception ignored) { }
|
||||
//
|
||||
// // 3) If auth.getName() is actually a numeric id (some JWT setups do this)
|
||||
// try {
|
||||
// String name = auth.getName();
|
||||
// if (name != null && name.matches("^\\d+$")) {
|
||||
// return Integer.parseInt(name);
|
||||
// }
|
||||
// } catch (Exception ignored) { }
|
||||
//
|
||||
// // 4) If principal is a Map-like structure with "userId"/"id" (some token decoders)
|
||||
// if (principal instanceof Map<?, ?> map) {
|
||||
// Object id = map.get("userId");
|
||||
// if (id == null) id = map.get("id");
|
||||
// if (id != null) {
|
||||
// try { return Integer.parseInt(String.valueOf(id)); } catch (Exception ignored) {}
|
||||
// }
|
||||
// }
|
||||
// System.out.println("PRINCIPAL=" + auth.getPrincipal().getClass() + " :: " + auth.getPrincipal());
|
||||
// System.out.println("NAME=" + auth.getName());
|
||||
// return null;
|
||||
// }
|
||||
private Integer resolveUserId(Authentication auth) {
|
||||
// DEV MODE ONLY
|
||||
// TODO: Replace with above once auth is wired end-to-end
|
||||
|
||||
return 3; // <- pick a user_id that exists in your users table
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,109 @@
|
||||
package group.goforward.battlbuilder.model;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "builds")
|
||||
public class Build {
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Primary key (Postgres GENERATED ALWAYS AS IDENTITY)
|
||||
// -----------------------------------------------------
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id", nullable = false)
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "account_id", nullable = false)
|
||||
private Integer accountId;
|
||||
// -----------------------------------------------------
|
||||
// UUID (DB default gen_random_uuid())
|
||||
// -----------------------------------------------------
|
||||
@ColumnDefault("gen_random_uuid()")
|
||||
@Column(name = "uuid", nullable = false)
|
||||
private UUID uuid;
|
||||
|
||||
@Column(name = "name", nullable = false)
|
||||
private String name;
|
||||
// -----------------------------------------------------
|
||||
// Ownership (nullable; ON DELETE SET NULL)
|
||||
// NOTE: Keeping this as Integer for now to avoid forcing a User entity relationship
|
||||
// -----------------------------------------------------
|
||||
@Column(name = "user_id")
|
||||
private Integer userId;
|
||||
|
||||
@Column(name = "description", length = Integer.MAX_VALUE)
|
||||
// -----------------------------------------------------
|
||||
// Main fields
|
||||
// -----------------------------------------------------
|
||||
@Column(name = "title", nullable = false)
|
||||
private String title;
|
||||
|
||||
@Column(name = "description")
|
||||
private String description;
|
||||
|
||||
@ColumnDefault("false")
|
||||
@Column(name = "is_public", nullable = false)
|
||||
private Boolean isPublic = false;
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Timestamps
|
||||
// -----------------------------------------------------
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private Instant updatedAt;
|
||||
|
||||
@ColumnDefault("now()")
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
@Column(name = "deleted_at")
|
||||
private Instant deletedAt;
|
||||
private OffsetDateTime deletedAt;
|
||||
|
||||
@ColumnDefault("gen_random_uuid()")
|
||||
@Column(name = "uuid")
|
||||
private UUID uuid;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
// -----------------------------------------------------
|
||||
// Hibernate lifecycle
|
||||
// -----------------------------------------------------
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
if (uuid == null) uuid = UUID.randomUUID();
|
||||
OffsetDateTime now = OffsetDateTime.now();
|
||||
if (createdAt == null) createdAt = now;
|
||||
if (updatedAt == null) updatedAt = now;
|
||||
if (isPublic == null) isPublic = false;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
updatedAt = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
public Integer getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
// -----------------------------------------------------
|
||||
// Getters / Setters
|
||||
// -----------------------------------------------------
|
||||
public Integer getId() { return id; }
|
||||
public void setId(Integer id) { this.id = id; }
|
||||
|
||||
public void setAccountId(Integer accountId) {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
public UUID getUuid() { return uuid; }
|
||||
public void setUuid(UUID uuid) { this.uuid = uuid; }
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public Integer getUserId() { return userId; }
|
||||
public void setUserId(Integer userId) { this.userId = userId; }
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
public Boolean getIsPublic() { return isPublic; }
|
||||
public void setIsPublic(Boolean isPublic) { this.isPublic = isPublic; }
|
||||
|
||||
public Instant getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
public OffsetDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(OffsetDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public void setUpdatedAt(Instant updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Instant getDeletedAt() {
|
||||
return deletedAt;
|
||||
}
|
||||
|
||||
public void setDeletedAt(Instant deletedAt) {
|
||||
this.deletedAt = deletedAt;
|
||||
}
|
||||
|
||||
public UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
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; }
|
||||
}
|
||||
@@ -60,6 +60,18 @@ public class BuildItem {
|
||||
@Column(name = "deleted_at")
|
||||
private OffsetDateTime deletedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
if (this.uuid == null) this.uuid = java.util.UUID.randomUUID();
|
||||
if (this.createdAt == null) this.createdAt = OffsetDateTime.now();
|
||||
if (this.updatedAt == null) this.updatedAt = this.createdAt;
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
this.updatedAt = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,20 @@ package group.goforward.battlbuilder.repos;
|
||||
|
||||
import group.goforward.battlbuilder.model.BuildItem;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BuildItemRepository extends JpaRepository<BuildItem, Integer> {
|
||||
List<BuildItem> findByBuildId(Integer buildId);
|
||||
Optional<BuildItem> findByUuid(UUID uuid);
|
||||
|
||||
// main “load build” query
|
||||
List<BuildItem> findByBuild_Uuid(UUID buildUuid);
|
||||
|
||||
// handy for cleanup or listing items
|
||||
List<BuildItem> findByBuild_Id(Integer buildId);
|
||||
|
||||
@Transactional
|
||||
void deleteByBuild_Id(Integer buildId);
|
||||
}
|
||||
@@ -2,9 +2,20 @@ package group.goforward.battlbuilder.repos;
|
||||
|
||||
import group.goforward.battlbuilder.model.Build;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BuildRepository extends JpaRepository<Build, Integer> {
|
||||
Optional<Build> findByUuid(UUID uuid);
|
||||
|
||||
// My builds list (excludes soft-deleted)
|
||||
List<Build> findByUserIdAndDeletedAtIsNullOrderByUpdatedAtDesc(Integer userId);
|
||||
|
||||
// Optional: if you want /me/builds/{uuid} fetch with ownership checks
|
||||
Optional<Build> findByUuidAndDeletedAtIsNull(UUID uuid);
|
||||
|
||||
// Optional: owner-only lookups
|
||||
Optional<Build> findByUuidAndUserIdAndDeletedAtIsNull(UUID uuid, Integer userId);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package group.goforward.battlbuilder.web.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BuildCreateRequest {
|
||||
|
||||
// main build info
|
||||
private String title;
|
||||
private String description;
|
||||
private Boolean isPublic;
|
||||
|
||||
// items = your builder slots; for now, slot can be the CategoryId string
|
||||
private List<BuildItemCreateRequest> items;
|
||||
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Boolean getIsPublic() { return isPublic; }
|
||||
public void setIsPublic(Boolean isPublic) { this.isPublic = isPublic; }
|
||||
|
||||
public List<BuildItemCreateRequest> getItems() { return items; }
|
||||
public void setItems(List<BuildItemCreateRequest> items) { this.items = items; }
|
||||
|
||||
public static class BuildItemCreateRequest {
|
||||
private String slot;
|
||||
private Integer productId;
|
||||
private Integer position;
|
||||
private Integer quantity;
|
||||
|
||||
public String getSlot() { return slot; }
|
||||
public void setSlot(String slot) { this.slot = slot; }
|
||||
|
||||
public Integer getProductId() { return productId; }
|
||||
public void setProductId(Integer productId) { this.productId = productId; }
|
||||
|
||||
public Integer getPosition() { return position; }
|
||||
public void setPosition(Integer position) { this.position = position; }
|
||||
|
||||
public Integer getQuantity() { return quantity; }
|
||||
public void setQuantity(Integer quantity) { this.quantity = quantity; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package group.goforward.battlbuilder.web.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BuildDto {
|
||||
private String id;
|
||||
private UUID uuid;
|
||||
|
||||
private String title;
|
||||
private String description;
|
||||
private Boolean isPublic;
|
||||
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
private List<BuildItemDto> items;
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public UUID getUuid() { return uuid; }
|
||||
public void setUuid(UUID uuid) { this.uuid = uuid; }
|
||||
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public Boolean getIsPublic() { return isPublic; }
|
||||
public void setIsPublic(Boolean isPublic) { this.isPublic = isPublic; }
|
||||
|
||||
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 List<BuildItemDto> getItems() { return items; }
|
||||
public void setItems(List<BuildItemDto> items) { this.items = items; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package group.goforward.battlbuilder.web.dto;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BuildItemDto {
|
||||
private String id;
|
||||
private UUID uuid;
|
||||
|
||||
private String slot;
|
||||
private Integer position;
|
||||
private Integer quantity;
|
||||
|
||||
private String productId;
|
||||
private String productName;
|
||||
private String productBrand;
|
||||
private String productImageUrl;
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public UUID getUuid() { return uuid; }
|
||||
public void setUuid(UUID uuid) { this.uuid = uuid; }
|
||||
|
||||
public String getSlot() { return slot; }
|
||||
public void setSlot(String slot) { this.slot = slot; }
|
||||
|
||||
public Integer getPosition() { return position; }
|
||||
public void setPosition(Integer position) { this.position = position; }
|
||||
|
||||
public Integer getQuantity() { return quantity; }
|
||||
public void setQuantity(Integer quantity) { this.quantity = quantity; }
|
||||
|
||||
public String getProductId() { return productId; }
|
||||
public void setProductId(String productId) { this.productId = productId; }
|
||||
|
||||
public String getProductName() { return productName; }
|
||||
public void setProductName(String productName) { this.productName = productName; }
|
||||
|
||||
public String getProductBrand() { return productBrand; }
|
||||
public void setProductBrand(String productBrand) { this.productBrand = productBrand; }
|
||||
|
||||
public String getProductImageUrl() { return productImageUrl; }
|
||||
public void setProductImageUrl(String productImageUrl) { this.productImageUrl = productImageUrl; }
|
||||
}
|
||||
Reference in New Issue
Block a user