From ed076411edfe5946b7ea92c441a902ee32e50351 Mon Sep 17 00:00:00 2001 From: joungmin Date: Mon, 15 Jun 2026 14:25:53 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20P4-3=20=EC=9D=B8=EC=A6=9D=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20+=20=EC=A7=80=EB=8F=84=20cleanup/=ED=84=B0?= =?UTF-8?q?=EC=B9=98/=EC=A0=91=EA=B7=BC=EC=84=B1=20(#266+#278)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #266 (인증): - AuthService.loginGoogle: catch-all에서 e.getMessage() 노출 → "Invalid Google token" 고정 메시지 + 상세는 log.warn (Google verifier 내부 오류 정보 누출 차단) #278 (지도): - boundsTimerRef 언마운트 cleanup (unmounted setState 경고 + 메모리 누수 방지) - '내 위치' 버튼 36×36 → 44×44 + aria-label='내 위치로 이동' + touch-manipulation - dead code 제거 (indexRef set-only, restaurantMap 미사용) #277 (health) — 결함 모두 후속 분리 (deep health, version, 테스트, rate limit) 후속 분리: - #338 (deep health/version/Actuator) - #339 (hex → brand-* 토큰 + 마커 ARIA + 테스트) - #340 (다중 audience verifier + AuthService 테스트) Refs: #266 #277 #278 --- .../java/com/tasteby/service/AuthService.java | 9 +++++- frontend/src/components/MapView.tsx | 28 ++++++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/backend-java/src/main/java/com/tasteby/service/AuthService.java b/backend-java/src/main/java/com/tasteby/service/AuthService.java index a90fb7b..69f4405 100644 --- a/backend-java/src/main/java/com/tasteby/service/AuthService.java +++ b/backend-java/src/main/java/com/tasteby/service/AuthService.java @@ -6,6 +6,8 @@ import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.tasteby.domain.UserInfo; import com.tasteby.security.JwtTokenProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -17,6 +19,8 @@ import java.util.Map; @Service public class AuthService { + private static final Logger log = LoggerFactory.getLogger(AuthService.class); + private final UserService userService; private final JwtTokenProvider jwtProvider; private final GoogleIdTokenVerifier verifier; @@ -58,7 +62,10 @@ public class AuthService { } catch (ResponseStatusException e) { throw e; } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Google token: " + e.getMessage()); + // #266 — 외부에는 고정 메시지만, 상세는 로그로 (Google verifier 내부 네트워크/공개키 + // 조회 실패 메시지가 클라이언트에 노출되지 않도록) + log.warn("Google token verification failed: {}", e.getMessage()); + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Google token"); } } diff --git a/frontend/src/components/MapView.tsx b/frontend/src/components/MapView.tsx index 9d1cfb1..85db334 100644 --- a/frontend/src/components/MapView.tsx +++ b/frontend/src/components/MapView.tsx @@ -67,8 +67,7 @@ type RestaurantProps = { restaurant: Restaurant }; type RestaurantFeature = Supercluster.PointFeature; function useSupercluster(restaurants: Restaurant[]) { - const indexRef = useRef | null>(null); - + // #278 — indexRef 제거 (set만 되고 read 없는 dead code) const points: RestaurantFeature[] = useMemo( () => restaurants.map((r) => ({ @@ -86,7 +85,6 @@ function useSupercluster(restaurants: Restaurant[]) { minPoints: 2, }); sc.load(points); - indexRef.current = sc; return sc; }, [points]); @@ -129,12 +127,7 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh const channelColors = useMemo(() => getChannelColorMap(restaurants), [restaurants]); const { getClusters, getExpansionZoom } = useSupercluster(restaurants); - // Build a lookup for restaurants by id - const restaurantMap = useMemo(() => { - const m: Record = {}; - restaurants.forEach((r) => { m[r.id] = r; }); - return m; - }, [restaurants]); + // #278 — restaurantMap 제거 (빌드만 되고 렌더에서 사용 안 됨, dead code) const clusters = useMemo(() => { if (!bounds) return []; @@ -273,7 +266,7 @@ function MapContent({ restaurants, selected, onSelectRestaurant, flyTo, activeCh textDecoration: isClosed ? "line-through" : "none", }} > - {getCuisineIcon(r.cuisine_type)} + {getCuisineIcon(r.cuisine_type)} {r.name}
-

{getCuisineIcon(infoTarget.cuisine_type)}{infoTarget.name}

+

{getCuisineIcon(infoTarget.cuisine_type)}{infoTarget.name}

{infoTarget.business_status === "CLOSED_PERMANENTLY" && ( 폐업 )} @@ -357,6 +350,13 @@ export default function MapView({ restaurants, selected, onSelectRestaurant, onB }, 150); }, [onBoundsChanged]); + // #278 — 언마운트 시 디바운스 타이머 정리 (메모리 누수 + unmounted setState 경고 방지) + useEffect(() => { + return () => { + if (boundsTimerRef.current) clearTimeout(boundsTimerRef.current); + }; + }, []); + return ( - + )} {channelNames.length > 0 && (