Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation 'com.opencsv:opencsv:5.12.0'
annotationProcessor 'org.projectlombok:lombok'

// Email & SMS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAsync
@EnableScheduling
@EnableCaching
public class HatfieldBackendApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.nakamas.hatfieldbackend.controllers;

import com.nakamas.hatfieldbackend.models.entities.prices.Pricing;
import com.nakamas.hatfieldbackend.models.views.incoming.PricingView;
import com.nakamas.hatfieldbackend.models.views.outgoing.PricingEvaluation;
import com.nakamas.hatfieldbackend.services.PricingService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/price")
public class PricingController {
private final PricingService pricingService;

@GetMapping("/evaluate")
public PricingEvaluation evaluate(
@RequestParam String deviceType,
@RequestParam String brand,
@RequestParam String model,
@RequestParam String issue) {
return pricingService.evaluate(deviceType, brand, model, issue);
}

@GetMapping("/worker/pricings")
public List<PricingView> getAllPricings() {
return pricingService.getAllPricings();
}
@GetMapping("/worker/pricings/filter")
public List<PricingView> getPricingsWithFilters(
@RequestParam(required = false) String deviceType,
@RequestParam(required = false) String brand,
@RequestParam(required = false) String model) {
return pricingService.getPricingsWithFilters(deviceType, brand, model);
}
@PostMapping("/worker/pricings")
public Pricing createPricing(@RequestBody PricingView pricing) {

return pricingService.save(pricing);
}

@PutMapping("/worker/pricings/{id}")
public Pricing updatePricing(@PathVariable Long id, @RequestBody PricingView pricing) {
return pricingService.save(pricing, id);
}

@DeleteMapping("/worker/pricings/{id}")
public void deletePricing(@PathVariable Long id) {
pricingService.delete(id);
}
@GetMapping("/worker/pricings/csv")
public ResponseEntity<byte[]> downloadCsv() {
List<PricingView> pricings = pricingService.getAllPricings();
byte[] csvBytes = pricingService.exportToCsv(pricings);

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=pricings.csv")
.contentType(MediaType.parseMediaType("text/csv"))
.body(csvBytes);
}

@PostMapping("/worker/pricings/csv")
public ResponseEntity<Void> uploadCsv(@RequestParam("file") MultipartFile file) {
pricingService.importFromCsv(file);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@

import com.nakamas.hatfieldbackend.config.exception.CustomException;
import com.nakamas.hatfieldbackend.models.views.outgoing.ResponseMessage;
import com.nakamas.hatfieldbackend.models.views.outgoing.inventory.BrandView;
import com.nakamas.hatfieldbackend.models.views.outgoing.shop.ShopView;
import com.nakamas.hatfieldbackend.services.InventoryItemService;
import com.nakamas.hatfieldbackend.services.ShopService;
import com.nakamas.hatfieldbackend.services.UserService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequiredArgsConstructor
@RequestMapping("api/public")
public class PublicController {
private final UserService userService;
private final ShopService shopService;
private final InventoryItemService inventoryItemService;
private final Map<String, Integer> requestMap = new HashMap<>();

@PostMapping("forgot-password")
Expand All @@ -28,6 +32,15 @@ public ResponseMessage editPassword(@RequestParam String username, HttpServletRe
requestMap.put(request.getRemoteAddr(), requestCount + 1);
return responseMessage;
}
//todo: caches for /shop and /brands as they could be called without a profile.
@GetMapping("/shop")
public ShopView getShopPublicData(@RequestParam(name = "shopName") String name){
return shopService.getShopByName(name);
}
@GetMapping("/brands")
public List<BrandView> getBrandsForShop(){
return inventoryItemService.getAllBrands();
}

private int limitUserRequestsByIp(HttpServletRequest request) {
int MAX_REQUESTS_PER_HOUR = 2;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.nakamas.hatfieldbackend.models.entities.prices;

import com.nakamas.hatfieldbackend.models.entities.ticket.Brand;
import com.nakamas.hatfieldbackend.models.entities.ticket.Model;
import com.nakamas.hatfieldbackend.models.views.incoming.PricingView;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.math.BigDecimal;

@Getter
@Setter
@Entity
@Table(name = "pricings")
@NoArgsConstructor
public class Pricing {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String deviceType;
private Long brandId;
private Long modelId;
private String issue;
private BigDecimal price;
private BigDecimal originalPrice;

public Pricing(PricingView view, Brand brand, Model modelId) {
this.deviceType = view.deviceType();
this.brandId = brand.getId();
this.modelId = modelId.getId();
this.issue = view.issue();
this.price = view.price();
this.originalPrice = view.originalPrice();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,29 @@
@AllArgsConstructor
@Embeddable
public class ShopPageTemplates {
public static final String WELWYNHATFIELD = "WELWYNHATFIELD";
@Column()
private String name;

@Column(columnDefinition = "text")
private String aboutPage;


public ShopPageTemplates() {
this.name = WELWYNHATFIELD;
this.aboutPage = "# About us";
}

public ShopPageTemplates(ShopPageTemplatesView view) {
this();
if (view != null && view.getAboutPage() != null && view.getAboutPage().isBlank()) {
this.name = view.getTemplateName();
this.aboutPage = view.getAboutPage();
} else this.aboutPage = "# About us";
}
}

public void update(ShopPageTemplatesView view) {
if (view.getTemplateName()!=null) this.name = view.getTemplateName();
if (view.getAboutPage() != null) this.aboutPage = view.getAboutPage();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.nakamas.hatfieldbackend.models.views.incoming;

import com.nakamas.hatfieldbackend.models.entities.prices.Pricing;

import java.math.BigDecimal;

public record PricingView(
Long id,
String deviceType,
String brand,
String model,
String issue,
BigDecimal price,
BigDecimal originalPrice) {

public PricingView(Pricing p, String brand, String model){
this(p.getId(), p.getDeviceType(), brand, model, p.getIssue(), p.getPrice(), p.getOriginalPrice());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
public class ShopPageTemplatesView {
@Column(columnDefinition = "text")
private String aboutPage;
private String templateName;

public ShopPageTemplatesView(ShopPageTemplates templates) {
this(templates != null ? templates.getAboutPage() : "");
this(templates != null ? templates.getAboutPage() : "", ShopPageTemplates.WELWYNHATFIELD);
}

public void fillTemplates(Shop shop) {
Expand All @@ -31,4 +32,11 @@ public String getAboutPage() {
return aboutPage;
}

public String getTemplateName() {
return templateName;
}

public void setTemplateName(String templateName) {
this.templateName = templateName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nakamas.hatfieldbackend.models.views.outgoing;

import java.math.BigDecimal;

public record PricingEvaluation(
BigDecimal price,
BigDecimal originalPrice,
boolean priceExists,
String action
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ public interface BrandRepository extends JpaRepository<Brand, Long> {
List<BrandView> findAllBrands();

@Query("from Brand b where LOWER(b.brand) = LOWER(?1)")
Brand findByName(String brandValue);
List<Brand> findByName(String brandValue);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ public interface ModelRepository extends JpaRepository<Model, Long> {
@Query("select m " +
"from Model m " +
"where LOWER(m.model) like LOWER(?1) and m.brandId = ?2")
Model findByName(String name, Long brandId);
List<Model> findByName(String name, Long brandId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.nakamas.hatfieldbackend.repositories;

import com.nakamas.hatfieldbackend.models.entities.prices.Pricing;
import com.nakamas.hatfieldbackend.models.views.incoming.PricingView;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface PricingRepository extends JpaRepository<Pricing, Long> {
@Query("""
from Pricing p
where p.deviceType=?1 and
p.brandId=?2 and
p.modelId=?3 and
p.issue=?4
""")
Optional<Pricing> findByDeviceTypeAndBrandIdAndModelAndIssue(String deviceType, Long brandId, Long modelId, String issue);

@Query("""
select new com.nakamas.hatfieldbackend.models.views.incoming.PricingView(p, b.brand, m.model)
from Pricing p
join Brand b on b.id = p.brandId
join Model m on m.id = p.modelId
""")
List<PricingView> findAllPricingViews();

@Query("""
select new com.nakamas.hatfieldbackend.models.views.incoming.PricingView(p, b.brand, m.model)
from Pricing p
join Brand b on b.id = p.brandId
join Model m on m.id = p.modelId
where (:deviceType is null or p.deviceType = :deviceType)
and (:brand is null or b.brand = :brand)
and (:model is null or m.model = :model)
""")
List<PricingView> findAllWithFilters(
@Param("deviceType") String deviceType,
@Param("brand") String brand,
@Param("model") String model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.nakamas.hatfieldbackend.repositories.*;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;

Expand All @@ -30,6 +31,7 @@
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class InventoryItemService {
Expand All @@ -46,7 +48,7 @@ public class InventoryItemService {
public InventoryItem createInventoryItem(CreateInventoryItem inventoryItem) {
Brand brand = getOrCreateBrand(inventoryItem.brandId(), inventoryItem.brand());
Model model = getOrCreateModel(inventoryItem.modelId(), inventoryItem.model(), brand);
if (brand !=null && !brand.getModels().contains(model)) brand.getModels().add(model);
if (brand != null && !brand.getModels().contains(model)) brand.getModels().add(model);
Optional<Category> category = Optional.empty();
if (inventoryItem.categoryId() != null) {
category = categoryRepository.findById(inventoryItem.categoryId());
Expand Down Expand Up @@ -163,11 +165,14 @@ public Model getOrCreateModel(Long modelId, String modelValue, Brand brand) {
public Model getOrCreateModel(String modelValue, Brand brand) {
if (modelValue == null || modelValue.isBlank() || brand == null) return null;
Long brandId = brand.getId();
Model existingByName = modelRepository.findByName(modelValue, brandId);
if (existingByName != null) {
if (brand.getModels().stream().noneMatch((model)-> Objects.equals(model.getId(), existingByName.getId())))
brand.getModels().add(new Model(existingByName.getModel(), brandId));
return existingByName;
List<Model> existingByName = modelRepository.findByName(modelValue, brandId);
if (!existingByName.isEmpty()) {
if (existingByName.size() > 1)
log.warn("Duplicate models " + existingByName.stream().map(e -> "{%s,%s}".formatted(e.getId(), e.getModel())).collect(Collectors.joining(", ")));
Model existing = existingByName.get(0);
if (brand.getModels().stream().noneMatch((model) -> Objects.equals(model.getId(), existing.getId())))
brand.getModels().add(new Model(existing.getModel(), brandId));
return existing;
}
Model save = modelRepository.save(new Model(modelValue, brandId));
brand.getModels().add(save);
Expand All @@ -182,8 +187,10 @@ public Brand getOrCreateBrand(Long brandId, String brandValue) {

public Brand getOrCreateBrand(String brandValue) {
if (brandValue == null || brandValue.isBlank()) return null;
Brand existingByName = brandRepository.findByName(brandValue);
if (existingByName != null) return existingByName;
List<Brand> existingByName = brandRepository.findByName(brandValue);
if (existingByName.size() > 1)
log.warn("Duplicate models " + existingByName.stream().map(e -> "{%s,%s}".formatted(e.getId(), e.getBrand())).collect(Collectors.joining(", ")));
if (!existingByName.isEmpty()) return existingByName.get(0);
return brandRepository.save(new Brand(brandValue));
}

Expand Down
Loading
Loading