feat(backend): #359 1단계 — google_place_id 중복 조회 API

- GET /api/admin/restaurants/duplicates/place-id (어드민 전용)
- 그룹별 식당 목록 + video/review/memo 카운트 동봉
- Mapper: findDuplicatePlaceIdRows + Service 그룹핑
- 정리/병합 + UNIQUE 제약은 데이터 위험 분리 위해 후속 PR로

Refs: #359 (조회 단계 완료)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-06-15 20:32:40 +09:00
parent c1050f3abd
commit d73947444f
6 changed files with 100 additions and 0 deletions

View File

@@ -62,6 +62,15 @@ public class AdminRestaurantController {
return Map.of("success", true, "id", id);
}
// #359 1단계 — google_place_id 중복 조회 (정리/UNIQUE는 후속).
@GetMapping("/duplicates/place-id")
public Map<String, Object> duplicatePlaceIds() {
var admin = AuthUtil.requireAdmin();
var groups = restaurantService.findDuplicatePlaceIdGroups();
log.info("[ADMIN] {} duplicate place_id groups: {}", admin.getSubject(), groups.size());
return Map.of("groups", groups, "group_count", groups.size());
}
/**
* 어드민용 hidden 토글.
*/

View File

@@ -39,6 +39,9 @@ public interface RestaurantMapper {
int countUnevaluatedLinks();
// #359 1단계 — google_place_id 중복 조회
List<Map<String, Object>> findDuplicatePlaceIdRows();
Restaurant findById(@Param("id") String id);
List<Map<String, Object>> findVideoLinks(@Param("restaurantId") String restaurantId,

View File

@@ -111,6 +111,23 @@ public class RestaurantService {
return mapper.countUnevaluatedLinks();
}
// #359 1단계 — google_place_id 중복 그룹 (참조 카운트 동봉)
public List<Map<String, Object>> findDuplicatePlaceIdGroups() {
var rows = mapper.findDuplicatePlaceIdRows().stream()
.map(JsonUtil::lowerKeys)
.toList();
Map<String, List<Map<String, Object>>> grouped = new LinkedHashMap<>();
for (var r : rows) {
String key = (String) r.get("google_place_id");
grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(r);
}
List<Map<String, Object>> out = new ArrayList<>(grouped.size());
for (var e : grouped.entrySet()) {
out.add(Map.of("google_place_id", e.getKey(), "items", e.getValue()));
}
return out;
}
public void update(String id, Map<String, Object> fields) {
mapper.updateFields(id, fields);
}

View File

@@ -353,4 +353,21 @@
SELECT COUNT(*) FROM video_restaurants WHERE relevance_evaluated_at IS NULL
</select>
<!-- #359 1단계 — google_place_id 중복 조회 (그룹 식당 + 참조 카운트) -->
<select id="findDuplicatePlaceIdRows" resultType="map">
SELECT r.id, r.google_place_id, r.name, r.address,
TO_CHAR(r.created_at, 'YYYY-MM-DD"T"HH24:MI:SS') AS created_at,
r.hidden,
(SELECT COUNT(*) FROM video_restaurants vr WHERE vr.restaurant_id = r.id) AS video_count,
(SELECT COUNT(*) FROM user_reviews rv WHERE rv.restaurant_id = r.id) AS review_count,
(SELECT COUNT(*) FROM user_memos mm WHERE mm.restaurant_id = r.id) AS memo_count
FROM restaurants r
WHERE r.google_place_id IN (
SELECT google_place_id FROM restaurants
WHERE google_place_id IS NOT NULL
GROUP BY google_place_id HAVING COUNT(*) > 1
)
ORDER BY r.google_place_id, r.created_at
</select>
</mapper>