profileShare

rasmusjy / tasteprint

Read-only snapshot

No repository description.

main default branch 169 files Expires Sep 13, 2026, 9:06 AM
CatalogService.java 4,927 bytes
1 package com.tasteprint.catalog;
2
3 import java.util.List;
4 import java.util.Locale;
5 import java.util.Map;
6 import java.util.function.Function;
7 import java.util.stream.Collectors;
8
9 import org.springframework.data.domain.PageRequest;
10 import org.springframework.stereotype.Service;
11 import org.springframework.transaction.annotation.Transactional;
12
13 import com.tasteprint.shared.NotFoundException;
14
15 @Service
16 public class CatalogService {
17
18 private final DestinationRepository destinations;
19 private final DishRepository dishes;
20
21 CatalogService(DestinationRepository destinations, DishRepository dishes) {
22 this.destinations = destinations;
23 this.dishes = dishes;
24 }
25
26 @Transactional(readOnly = true)
27 public List<DestinationSummary> destinations() {
28 Map<String, Long> counts = dishes.findAll().stream()
29 .collect(Collectors.groupingBy(Dish::destinationCode, Collectors.counting()));
30 return destinations.findAllByOrderByDisplayOrderAsc().stream()
31 .map(destination -> summary(destination, counts.getOrDefault(destination.code(), 0L).intValue()))
32 .toList();
33 }
34
35 @Transactional(readOnly = true)
36 public DestinationDetails destination(String code) {
37 String normalizedCode = normalizeCountryCode(code);
38 Destination destination = requiredDestination(normalizedCode);
39 List<Dish> destinationDishes = dishes.findByDestinationCodeOrderByDisplayOrderAsc(normalizedCode);
40 return new DestinationDetails(
41 summary(destination, destinationDishes.size()),
42 destinationDishes.stream().map(dish -> view(dish, destination)).toList()
43 );
44 }
45
46 @Transactional(readOnly = true)
47 public DishView dish(String slug) {
48 Dish dish = requiredDish(slug);
49 return view(dish, requiredDestination(dish.destinationCode()));
50 }
51
52 @Transactional(readOnly = true)
53 public List<DishView> dishesFor(String destinationCode) {
54 Destination destination = requiredDestination(normalizeCountryCode(destinationCode));
55 return dishes.findByDestinationCodeOrderByDisplayOrderAsc(destination.code()).stream()
56 .map(dish -> view(dish, destination))
57 .toList();
58 }
59
60 @Transactional(readOnly = true)
61 public List<DishView> allDishes() {
62 Map<String, Destination> byCode = destinations.findAll().stream()
63 .collect(Collectors.toMap(Destination::code, Function.identity()));
64 return dishes.findAll().stream()
65 .map(dish -> view(dish, byCode.get(dish.destinationCode())))
66 .toList();
67 }
68
69 @Transactional(readOnly = true)
70 public List<DishView> search(String query) {
71 if (query == null || query.isBlank()) {
72 return List.of();
73 }
74 Map<String, Destination> byCode = destinations.findAll().stream()
75 .collect(Collectors.toMap(Destination::code, Function.identity()));
76 return dishes.search(query.trim(), PageRequest.of(0, 30)).stream()
77 .map(dish -> view(dish, byCode.get(dish.destinationCode())))
78 .toList();
79 }
80
81 @Transactional(readOnly = true)
82 public void requireDestination(String code) {
83 requiredDestination(normalizeCountryCode(code));
84 }
85
86 @Transactional(readOnly = true)
87 public void requireDish(String slug) {
88 requiredDish(slug);
89 }
90
91 private Destination requiredDestination(String code) {
92 return destinations.findById(code)
93 .orElseThrow(() -> new NotFoundException("Destination " + code + " is not in the catalog."));
94 }
95
96 private Dish requiredDish(String slug) {
97 return dishes.findById(slug)
98 .orElseThrow(() -> new NotFoundException("Dish was not found in the catalog."));
99 }
100
101 private String normalizeCountryCode(String code) {
102 if (code == null || !code.matches("(?i)[a-z]{2}")) {
103 throw new IllegalArgumentException("Destination must be a two-letter country code.");
104 }
105 return code.toUpperCase(Locale.ROOT);
106 }
107
108 private DestinationSummary summary(Destination destination, int dishCount) {
109 return new DestinationSummary(
110 destination.code(), destination.name(), destination.localName(), destination.regionName(),
111 destination.summary(), destination.centerLat(), destination.centerLng(),
112 destination.accentColor(), dishCount
113 );
114 }
115
116 private DishView view(Dish dish, Destination destination) {
117 return new DishView(
118 dish.slug(), dish.destinationCode(), destination.name(), destination.accentColor(),
119 dish.name(), dish.localName(), dish.category(), dish.category().label(), dish.description(),
120 dish.whyItMatters(), dish.importance(), dish.imageUrl(), dish.vegetarian(), dish.spicyLevel()
121 );
122 }
123 }
124