project compiles

This commit is contained in:
2025-11-29 10:18:54 -05:00
parent 50101820ce
commit 0baae3bc6c
23 changed files with 420 additions and 278 deletions

View File

@@ -4,18 +4,15 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EntityScan("group.goforward.ballistic.model")
@ComponentScan("group.goforward.ballistic.configuration")
@ComponentScan("group.goforward.ballistic.controllers")
@ComponentScan("group.goforward.ballistic.service")
@SpringBootApplication
@ComponentScan(basePackages = "group.goforward.ballistic")
@EntityScan(basePackages = "group.goforward.ballistic.model")
@EnableJpaRepositories(basePackages = "group.goforward.ballistic.repos")
public class BallisticApplication {
public static void main(String[] args) {
SpringApplication.run(BallisticApplication.class, args);
}
}
public static void main(String[] args) {
SpringApplication.run(BallisticApplication.class, args);
}
}

View File

@@ -0,0 +1,5 @@
package group.goforward.ballistic.imports;
public interface MerchantFeedImportService {
void importMerchantFeed(Integer merchantId);
}

View File

@@ -0,0 +1,16 @@
package group.goforward.ballistic.imports;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class MerchantFeedImportServiceImpl implements MerchantFeedImportService {
@Override
public void importMerchantFeed(Integer merchantId) {
// TODO: real import logic will be re-added.
// This stub exists to fix the repository/package mixup and get the project compiling again.
throw new UnsupportedOperationException("Merchant feed import not yet implemented after refactor.");
}
}

View File

@@ -0,0 +1,17 @@
package group.goforward.ballistic.imports.dto;
import java.math.BigDecimal;
public record MerchantFeedRow(
String brandName,
String productName,
String mpn,
String upc,
String avantlinkProductId,
String sku,
String categoryPath,
String buyUrl,
BigDecimal price,
BigDecimal originalPrice,
boolean inStock
) {}

View File

@@ -1,10 +1,8 @@
package group.goforward.ballistic.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.annotations.ColumnDefault;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.Instant;
import java.util.UUID;
@@ -12,39 +10,48 @@ import java.util.UUID;
@Entity
@Table(name = "brands")
public class Brand {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name", nullable = false, length = 100)
@Column(nullable = false, unique = true)
private String name;
@ColumnDefault("now()")
@Column(nullable = false, unique = true)
private UUID uuid;
@Column(unique = true)
private String slug;
@Column(name = "website")
private String website;
@Column(name = "logo_url")
private String logoUrl;
@CreationTimestamp
@Column(name = "created_at", nullable = false, updatable = false)
private Instant createdAt;
@UpdateTimestamp
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private Instant createdAt;
@Column(name = "deleted_at")
private Instant deletedAt;
@ColumnDefault("gen_random_uuid()")
@Column(name = "uuid")
private UUID uuid;
@Column(name = "url", length = Integer.MAX_VALUE)
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
@PrePersist
public void prePersist() {
if (uuid == null) {
uuid = UUID.randomUUID();
}
if (slug == null && name != null) {
slug = name.toLowerCase().replace(" ", "-");
}
}
// Getters and Setters
public Integer getId() {
return id;
}
@@ -61,12 +68,36 @@ public class Brand {
this.name = name;
}
public Instant getUpdatedAt() {
return updatedAt;
public UUID getUuid() {
return uuid;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public String getLogoUrl() {
return logoUrl;
}
public void setLogoUrl(String logoUrl) {
this.logoUrl = logoUrl;
}
public Instant getCreatedAt() {
@@ -77,6 +108,14 @@ public class Brand {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
public Instant getDeletedAt() {
return deletedAt;
}
@@ -84,13 +123,4 @@ public class Brand {
public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
public UUID getUuid() {
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
}

View File

@@ -1,28 +1,27 @@
package group.goforward.ballistic.model;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.time.OffsetDateTime;
import java.time.Instant;
@Entity
@Table(name = "feed_imports")
public class FeedImport {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
// merchant_id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "merchant_id", nullable = false)
private Merchant merchant;
@ColumnDefault("now()")
@Column(name = "started_at", nullable = false)
private OffsetDateTime startedAt;
private Instant startedAt;
@Column(name = "finished_at")
private OffsetDateTime finishedAt;
private Instant finishedAt;
@Column(name = "rows_total")
private Integer rowsTotal;
@@ -36,21 +35,18 @@ public class FeedImport {
@Column(name = "rows_updated")
private Integer rowsUpdated;
@ColumnDefault("'running'")
@Column(name = "status", nullable = false, length = Integer.MAX_VALUE)
private String status;
@Column(name = "status", nullable = false)
private String status = "running";
@Column(name = "error_message", length = Integer.MAX_VALUE)
@Column(name = "error_message")
private String errorMessage;
// getters & setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Merchant getMerchant() {
return merchant;
}
@@ -59,19 +55,19 @@ public class FeedImport {
this.merchant = merchant;
}
public OffsetDateTime getStartedAt() {
public Instant getStartedAt() {
return startedAt;
}
public void setStartedAt(OffsetDateTime startedAt) {
public void setStartedAt(Instant startedAt) {
this.startedAt = startedAt;
}
public OffsetDateTime getFinishedAt() {
public Instant getFinishedAt() {
return finishedAt;
}
public void setFinishedAt(OffsetDateTime finishedAt) {
public void setFinishedAt(Instant finishedAt) {
this.finishedAt = finishedAt;
}
@@ -122,5 +118,4 @@ public class FeedImport {
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@@ -1,41 +1,35 @@
package group.goforward.ballistic.model;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.time.Instant;
@Entity
@Table(name = "price_history")
public class PriceHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
// product_offer_id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_offer_id", nullable = false)
private ProductOffer productOffer;
@Column(name = "price", nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@ColumnDefault("now()")
@Column(name = "recorded_at", nullable = false)
private OffsetDateTime recordedAt;
private Instant recordedAt;
// getters & setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ProductOffer getProductOffer() {
return productOffer;
}
@@ -52,12 +46,11 @@ public class PriceHistory {
this.price = price;
}
public OffsetDateTime getRecordedAt() {
public Instant getRecordedAt() {
return recordedAt;
}
public void setRecordedAt(OffsetDateTime recordedAt) {
public void setRecordedAt(Instant recordedAt) {
this.recordedAt = recordedAt;
}
}

View File

@@ -1,185 +1,98 @@
package group.goforward.ballistic.model;
import jakarta.persistence.*;
import org.hibernate.annotations.ColumnDefault;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // uses products_id_seq in Postgres
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "uuid", nullable = false, updatable = false)
private UUID uuid;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "brand_id", nullable = false)
private Brand brand;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "description", nullable = false, length = Integer.MAX_VALUE)
@Column(name = "slug", nullable = false)
private String slug;
@Column(name = "mpn")
private String mpn;
@Column(name = "upc")
private String upc;
@Column(name = "platform")
private String platform;
@Column(name = "part_role")
private String partRole;
@Column(name = "short_description")
private String shortDescription;
@Column(name = "description")
private String description;
@Column(name = "price", nullable = false)
private BigDecimal price;
@Column(name = "main_image_url")
private String mainImageUrl;
@Column(name = "reseller_id", nullable = false)
private Integer resellerId;
@Column(name = "category_id", nullable = false)
private Integer categoryId;
@ColumnDefault("0")
@Column(name = "stock_qty")
private Integer stockQty;
@ColumnDefault("now()")
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
@ColumnDefault("now()")
@Column(name = "created_at", nullable = false)
private Instant createdAt;
@Column(name = "updated_at")
private Instant updatedAt;
@Column(name = "deleted_at")
private Instant deletedAt;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "brand_id")
private Brand brand;
// --- lifecycle hooks ---
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "part_category_id", nullable = false)
private PartCategory partCategory;
@Column(name = "slug", nullable = false, length = Integer.MAX_VALUE)
private String slug;
@Column(name = "caliber", length = Integer.MAX_VALUE)
private String caliber;
@Column(name = "barrel_length_mm")
private Integer barrelLengthMm;
@Column(name = "gas_system", length = Integer.MAX_VALUE)
private String gasSystem;
@Column(name = "handguard_length_mm")
private Integer handguardLengthMm;
@Column(name = "image_url", length = Integer.MAX_VALUE)
private String imageUrl;
@Column(name = "spec_sheet_url", length = Integer.MAX_VALUE)
private String specSheetUrl;
@Column(name = "msrp", precision = 10, scale = 2)
private BigDecimal msrp;
@Column(name = "current_lowest_price", precision = 10, scale = 2)
private BigDecimal currentLowestPrice;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_lowest_merchant_id")
private Merchant currentLowestMerchant;
@ColumnDefault("true")
@Column(name = "is_active", nullable = false)
private Boolean isActive = false;
public Boolean getIsActive() {
return isActive;
@PrePersist
public void prePersist() {
if (uuid == null) {
uuid = UUID.randomUUID();
}
Instant now = Instant.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
@PreUpdate
public void preUpdate() {
updatedAt = Instant.now();
}
public Merchant getCurrentLowestMerchant() {
return currentLowestMerchant;
// --- getters & setters ---
public Integer getId() {
return id;
}
public void setCurrentLowestMerchant(Merchant currentLowestMerchant) {
this.currentLowestMerchant = currentLowestMerchant;
public void setId(Integer id) {
this.id = id;
}
public BigDecimal getCurrentLowestPrice() {
return currentLowestPrice;
public UUID getUuid() {
return uuid;
}
public void setCurrentLowestPrice(BigDecimal currentLowestPrice) {
this.currentLowestPrice = currentLowestPrice;
}
public BigDecimal getMsrp() {
return msrp;
}
public void setMsrp(BigDecimal msrp) {
this.msrp = msrp;
}
public String getSpecSheetUrl() {
return specSheetUrl;
}
public void setSpecSheetUrl(String specSheetUrl) {
this.specSheetUrl = specSheetUrl;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public Integer getHandguardLengthMm() {
return handguardLengthMm;
}
public void setHandguardLengthMm(Integer handguardLengthMm) {
this.handguardLengthMm = handguardLengthMm;
}
public String getGasSystem() {
return gasSystem;
}
public void setGasSystem(String gasSystem) {
this.gasSystem = gasSystem;
}
public Integer getBarrelLengthMm() {
return barrelLengthMm;
}
public void setBarrelLengthMm(Integer barrelLengthMm) {
this.barrelLengthMm = barrelLengthMm;
}
public String getCaliber() {
return caliber;
}
public void setCaliber(String caliber) {
this.caliber = caliber;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public PartCategory getPartCategory() {
return partCategory;
}
public void setPartCategory(PartCategory partCategory) {
this.partCategory = partCategory;
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public Brand getBrand() {
@@ -190,14 +103,6 @@ public class Product {
this.brand = brand;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
@@ -206,6 +111,54 @@ public class Product {
this.name = name;
}
public String getSlug() {
return slug;
}
public void setSlug(String slug) {
this.slug = slug;
}
public String getMpn() {
return mpn;
}
public void setMpn(String mpn) {
this.mpn = mpn;
}
public String getUpc() {
return upc;
}
public void setUpc(String upc) {
this.upc = upc;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getPartRole() {
return partRole;
}
public void setPartRole(String partRole) {
this.partRole = partRole;
}
public String getShortDescription() {
return shortDescription;
}
public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}
public String getDescription() {
return description;
}
@@ -214,44 +167,12 @@ public class Product {
this.description = description;
}
public BigDecimal getPrice() {
return price;
public String getMainImageUrl() {
return mainImageUrl;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getResellerId() {
return resellerId;
}
public void setResellerId(Integer resellerId) {
this.resellerId = resellerId;
}
public Integer getCategoryId() {
return categoryId;
}
public void setCategoryId(Integer categoryId) {
this.categoryId = categoryId;
}
public Integer getStockQty() {
return stockQty;
}
public void setStockQty(Integer stockQty) {
this.stockQty = stockQty;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
public void setMainImageUrl(String mainImageUrl) {
this.mainImageUrl = mainImageUrl;
}
public Instant getCreatedAt() {
@@ -262,6 +183,14 @@ public class Product {
this.createdAt = createdAt;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
}
public Instant getDeletedAt() {
return deletedAt;
}
@@ -269,5 +198,4 @@ public class Product {
public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
}

View File

@@ -0,0 +1,8 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Brand;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface BrandRepository extends JpaRepository<Brand, Integer> {
Optional<Brand> findByNameIgnoreCase(String name);
}

View File

@@ -0,0 +1,12 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.BuildsComponent;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface BuildItemRepository extends JpaRepository<BuildsComponent, Integer> {
List<BuildsComponent> findByBuildId(Integer buildId);
Optional<BuildsComponent> findByUuid(UUID uuid);
}

View File

@@ -0,0 +1,10 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Build;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface BuildRepository extends JpaRepository<Build, Integer> {
Optional<Build> findByUuid(UUID uuid);
}

View File

@@ -0,0 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.AffiliateCategoryMap;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CategoryMappingRepository extends JpaRepository<AffiliateCategoryMap, Integer> {
}

View File

@@ -0,0 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.FeedImport;
import org.springframework.data.jpa.repository.JpaRepository;
public interface FeedImportRepository extends JpaRepository<FeedImport, Long> {
}

View File

@@ -0,0 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.Merchant;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MerchantRepository extends JpaRepository<Merchant, Integer> {
}

View File

@@ -0,0 +1,9 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.PartCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface PartCategoryRepository extends JpaRepository<PartCategory, Integer> {
Optional<PartCategory> findBySlug(String slug);
}

View File

@@ -0,0 +1,7 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.PriceHistory;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PriceHistoryRepository extends JpaRepository<PriceHistory, Long> {
}

View File

@@ -0,0 +1,14 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.ProductOffer;
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.model.Merchant;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface ProductOfferRepository extends JpaRepository<ProductOffer, UUID> {
Optional<ProductOffer> findByMerchantAndAvantlinkProductId(Merchant merchant, String avantlinkProductId);
List<ProductOffer> findByProductAndInStockTrueOrderByPriceAsc(Product product);
}

View File

@@ -0,0 +1,11 @@
import group.goforward.ballistic.model.Product;
import group.goforward.ballistic.model.Brand;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface ProductRepository extends JpaRepository<Product, Integer> {
Optional<Product> findByUuid(UUID uuid);
Optional<Product> findByBrandAndMpn(Brand brand, String mpn);
Optional<Product> findByBrandAndUpc(Brand brand, String upc);
}

View File

@@ -0,0 +1,11 @@
package group.goforward.ballistic.repos;
import group.goforward.ballistic.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByEmail(String email);
Optional<User> findByUuid(UUID uuid);
}

View File

@@ -0,0 +1,22 @@
package group.goforward.ballistic.web;
import group.goforward.ballistic.imports.MerchantFeedImportService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/imports")
public class ImportController {
private final MerchantFeedImportService importService;
public ImportController(MerchantFeedImportService importService) {
this.importService = importService;
}
@PostMapping("/{merchantId}")
public ResponseEntity<Void> importMerchant(@PathVariable Integer merchantId) {
importService.importMerchantFeed(merchantId);
return ResponseEntity.accepted().build();
}
}

View File

@@ -0,0 +1,23 @@
package group.goforward.ballistic.web;
import group.goforward.ballistic.model.Merchant;
import group.goforward.ballistic.repos.MerchantRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class MerchantDebugController {
private final MerchantRepository merchantRepository;
public MerchantDebugController(MerchantRepository merchantRepository) {
this.merchantRepository = merchantRepository;
}
@GetMapping("/admin/debug/merchants")
public List<Merchant> listMerchants() {
return merchantRepository.findAll();
}
}

View File

@@ -0,0 +1,13 @@
package group.goforward.ballistic.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PingController {
@GetMapping("/ping")
public String ping() {
return "pong";
}
}

View File

@@ -1,6 +1,6 @@
spring.application.name=ballistic
# Database connection properties
spring.datasource.url=jdbc:postgresql://r710.gofwd.group:5433/ballistic
spring.datasource.url=jdbc:postgresql://r710.gofwd.group:5433/ss_builder
spring.datasource.username=postgres
spring.datasource.password=cul8rman
spring.datasource.driver-class-name=org.postgresql.Driver