- 모바일 하단 네비게이션(홈/식당목록/내주변/찜/내정보) 추가 - 로그인 버튼을 모달 방식으로 변경 (소셜 로그인 확장 가능) - 내위치 기반 정렬 및 영역 필터, 지도 내위치 버튼 추가 - 채널 필터 시 해당 채널만 마커/범례 표시 - 캐치테이블 검색/연동 (단건/벌크), NONE 저장 패턴 - 벌크 트랜스크립트 SSE (Playwright 브라우저 재사용) - 테이블링/캐치테이블 버튼 UI 차별화 - Google Maps 링크 모바일 호환, 초기화 버튼, 검색 라벨 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
171 lines
6.5 KiB
Java
171 lines
6.5 KiB
Java
package com.tasteby.service;
|
|
|
|
import com.tasteby.domain.Restaurant;
|
|
import com.tasteby.mapper.RestaurantMapper;
|
|
import com.tasteby.util.IdGenerator;
|
|
import com.tasteby.util.JsonUtil;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
@Service
|
|
public class RestaurantService {
|
|
|
|
private final RestaurantMapper mapper;
|
|
|
|
public RestaurantService(RestaurantMapper mapper) {
|
|
this.mapper = mapper;
|
|
}
|
|
|
|
public List<Restaurant> findAll(int limit, int offset, String cuisine, String region, String channel) {
|
|
List<Restaurant> restaurants = mapper.findAll(limit, offset, cuisine, region, channel);
|
|
enrichRestaurants(restaurants);
|
|
return restaurants;
|
|
}
|
|
|
|
public List<Restaurant> findWithoutTabling() {
|
|
return mapper.findWithoutTabling();
|
|
}
|
|
|
|
public List<Restaurant> findWithoutCatchtable() {
|
|
return mapper.findWithoutCatchtable();
|
|
}
|
|
|
|
public Restaurant findById(String id) {
|
|
Restaurant restaurant = mapper.findById(id);
|
|
if (restaurant == null) return null;
|
|
enrichRestaurants(List.of(restaurant));
|
|
return restaurant;
|
|
}
|
|
|
|
public List<Map<String, Object>> findVideoLinks(String restaurantId) {
|
|
var rows = mapper.findVideoLinks(restaurantId);
|
|
return rows.stream().map(row -> {
|
|
var m = JsonUtil.lowerKeys(row);
|
|
m.put("foods_mentioned", JsonUtil.parseStringList(m.get("foods_mentioned")));
|
|
m.put("evaluation", JsonUtil.parseMap(m.get("evaluation")));
|
|
m.put("guests", JsonUtil.parseStringList(m.get("guests")));
|
|
return m;
|
|
}).toList();
|
|
}
|
|
|
|
public void update(String id, Map<String, Object> fields) {
|
|
mapper.updateFields(id, fields);
|
|
}
|
|
|
|
@Transactional
|
|
public void delete(String id) {
|
|
mapper.deleteVectors(id);
|
|
mapper.deleteReviews(id);
|
|
mapper.deleteFavorites(id);
|
|
mapper.deleteVideoRestaurants(id);
|
|
mapper.deleteRestaurant(id);
|
|
}
|
|
|
|
public String upsert(Map<String, Object> data) {
|
|
String placeId = (String) data.get("google_place_id");
|
|
String existingId = null;
|
|
if (placeId != null && !placeId.isBlank()) {
|
|
existingId = mapper.findIdByPlaceId(placeId);
|
|
}
|
|
if (existingId == null) {
|
|
existingId = mapper.findIdByName((String) data.get("name"));
|
|
}
|
|
|
|
Restaurant r = Restaurant.builder()
|
|
.name(truncateBytes((String) data.get("name"), 200))
|
|
.address(truncateBytes((String) data.get("address"), 500))
|
|
.region((String) data.get("region"))
|
|
.latitude(data.get("latitude") instanceof Number n ? n.doubleValue() : null)
|
|
.longitude(data.get("longitude") instanceof Number n ? n.doubleValue() : null)
|
|
.cuisineType((String) data.get("cuisine_type"))
|
|
.priceRange((String) data.get("price_range"))
|
|
.googlePlaceId(placeId)
|
|
.phone((String) data.get("phone"))
|
|
.website((String) data.get("website"))
|
|
.businessStatus((String) data.get("business_status"))
|
|
.rating(data.get("rating") instanceof Number n ? n.doubleValue() : null)
|
|
.ratingCount(data.get("rating_count") instanceof Number n ? n.intValue() : null)
|
|
.build();
|
|
|
|
if (existingId != null) {
|
|
r.setId(existingId);
|
|
mapper.updateRestaurant(r);
|
|
return existingId;
|
|
} else {
|
|
String newId = IdGenerator.newId();
|
|
r.setId(newId);
|
|
mapper.insertRestaurant(r);
|
|
return newId;
|
|
}
|
|
}
|
|
|
|
public void linkVideoRestaurant(String videoId, String restaurantId, List<String> foods, String evaluation, List<String> guests) {
|
|
String id = IdGenerator.newId();
|
|
String foodsJson = foods != null ? JsonUtil.toJson(foods) : null;
|
|
String guestsJson = guests != null ? JsonUtil.toJson(guests) : null;
|
|
mapper.linkVideoRestaurant(id, videoId, restaurantId, foodsJson, evaluation, guestsJson);
|
|
}
|
|
|
|
public void updateCuisineType(String id, String cuisineType) {
|
|
mapper.updateCuisineType(id, cuisineType);
|
|
}
|
|
|
|
public void updateFoodsMentioned(String id, String foods) {
|
|
mapper.updateFoodsMentioned(id, foods);
|
|
}
|
|
|
|
public List<Map<String, Object>> findForRemapCuisine() {
|
|
return mapper.findForRemapCuisine();
|
|
}
|
|
|
|
public List<Map<String, Object>> findForRemapFoods() {
|
|
return mapper.findForRemapFoods();
|
|
}
|
|
|
|
private void enrichRestaurants(List<Restaurant> restaurants) {
|
|
if (restaurants.isEmpty()) return;
|
|
List<String> ids = restaurants.stream().map(Restaurant::getId).filter(Objects::nonNull).toList();
|
|
if (ids.isEmpty()) return;
|
|
|
|
// Channels
|
|
List<Map<String, Object>> channelRows = mapper.findChannelsByRestaurantIds(ids);
|
|
Map<String, List<String>> channelMap = new HashMap<>();
|
|
for (var row : channelRows) {
|
|
String rid = (String) row.getOrDefault("restaurant_id", row.get("RESTAURANT_ID"));
|
|
String ch = (String) row.getOrDefault("channel_name", row.get("CHANNEL_NAME"));
|
|
if (rid != null && ch != null) {
|
|
channelMap.computeIfAbsent(rid, k -> new ArrayList<>()).add(ch);
|
|
}
|
|
}
|
|
|
|
// Foods
|
|
List<Map<String, Object>> foodRows = mapper.findFoodsByRestaurantIds(ids);
|
|
Map<String, Set<String>> foodMap = new HashMap<>();
|
|
for (var row : foodRows) {
|
|
String rid = (String) row.getOrDefault("restaurant_id", row.get("RESTAURANT_ID"));
|
|
Object foods = row.getOrDefault("foods_mentioned", row.get("FOODS_MENTIONED"));
|
|
if (rid != null && foods != null) {
|
|
List<String> parsed = JsonUtil.parseStringList(foods);
|
|
foodMap.computeIfAbsent(rid, k -> new LinkedHashSet<>()).addAll(parsed);
|
|
}
|
|
}
|
|
|
|
for (var r : restaurants) {
|
|
r.setChannels(channelMap.getOrDefault(r.getId(), List.of()));
|
|
Set<String> foods = foodMap.get(r.getId());
|
|
r.setFoodsMentioned(foods != null ? new ArrayList<>(foods) : List.of());
|
|
}
|
|
}
|
|
|
|
private String truncateBytes(String s, int maxBytes) {
|
|
if (s == null) return null;
|
|
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
|
|
if (bytes.length <= maxBytes) return s;
|
|
return new String(bytes, 0, maxBytes, StandardCharsets.UTF_8);
|
|
}
|
|
}
|