Skip to content

Kakao map provider + POI name search overlay - #173

Merged
malkoG merged 9 commits into
devfrom
feature/kakao-map-providwr
Apr 17, 2026
Merged

malkoG merged 9 commits into
devfrom
feature/kakao-map-providwr

Conversation

@malkoG

@malkoG malkoG commented Apr 17, 2026 •

Copy link
Copy Markdown
Member

Summary

Introduces a pluggable user-facing map provider abstraction (OSM default, Kakao, Google stub) driven by MAP_PROVIDER env, and adds a floating POI name-search overlay on top of the interactive maps that hits Kakao's local keyword search and focuses the map on the picked result. OSM/OSS builds get the existing Leaflet behavior unchanged; Kakao deployments get richer place discovery for organizers on /events/create + check-in flows on /places.

Closes #166 (core: provider abstraction + Kakao place search; reverse geocoding and modal UI mode remain as follow-ups). Also partially addresses #110 and #116 — full forward geocoding via Nominatim and an abstract geocoding interface are out of scope here.


Assisted-By: Claude Code(claude-opus-4-7)

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for multiple map providers: OpenStreetMap (default), Kakao Maps, and Google Maps
    • Introduced map search overlay enabling point-of-interest searches biased by map location
    • Integrated POI search functionality across map views for enhanced place discovery
  • Chores

    • Extended environment configuration for map provider selection and credentials

malkoG added 9 commits April 17, 2026 14:14
Pulls marker SVG/emoji rendering out of LeafletMap into
src/components/maps/icon.ts so upcoming Kakao/Google adapters can
reuse the exact same artwork. Introduces a shared UserFacingMapProps
type in src/components/maps/types.ts. No behavior change.
Introduces MAP_PROVIDER (osm|kakao|google, default osm),
KAKAO_MAP_APP_KEY, and GOOGLE_MAPS_API_KEY. The new
/api/map-config controller returns the active provider and its
key; it fails loudly (HTTP 500) when the selected provider's key
is missing so misconfigured deployments surface rather than
silently falling back.

Also extends the CSP script-src with Kakao's SDK hosts only
when MAP_PROVIDER=kakao, keeping the OSS/OSM path's allowlist
untouched.
Introduces <UserFacingMap>, a dispatcher that reads /api/map-config
via react-query and renders one of three adapters sharing a common
UserFacingMapProps interface:

  * LeafletAdapter — wraps the existing LeafletMap (OSS/OSM default)
  * KakaoAdapter — Kakao Maps JS SDK, loads dapi.kakao.com script
    lazily and reuses the shared icon artwork for visual parity
  * GoogleAdapter — stub that throws until an SDK integration lands

On /api/map-config error (e.g. missing key) the dispatcher renders
a visible "Map unavailable" banner rather than silently falling
back to OSM, so operator misconfiguration is loud.

No call sites are migrated in this commit — the module is
introduced as inert code.
Swaps LeafletMap for UserFacingMap in the six user-facing call
sites — places list/detail maps, event detail thumbnail + modal
maps, and PlacePicker (selected + unselected). Props are
unchanged; the dispatcher picks an adapter at runtime based on
MAP_PROVIDER.

Admin-facing maps (admin/countries, admin/banners H3 coverage)
and the server-side static snapshot generator are intentionally
untouched — they remain on Leaflet/OSM.
Clicking the red "new place" pin dropped by a map-click would fall
through nearbyPlaces.find() and navigate to /places/new — "new"
is a synthetic marker id, not a real place, so the destination 404s.

Short-circuit id === "new" to open the existing check-in dialog
instead, matching the Check In button's behavior.
Introduces KAKAO_MAP_REST_KEY (server-side only) and a typed
poi-search service that wraps Kakao's local keyword search endpoint.
The /api/places/poi-search controller accepts q/lat/lng/radius and
returns a list of PoiCandidate results (name, address, road address,
coords, distance). Fails loudly with HTTP 500 when the REST key is
missing but MAP_PROVIDER=kakao.

Backs the upcoming search overlay on the interactive maps; gated by
MAP_PROVIDER so OSS/OSM deployments are unaffected (service throws
ServiceError for non-kakao providers). Includes a small in-memory
LRU cache keyed by (query, coarse-coord, radius) with 5-min TTL to
avoid hammering Kakao on typeahead keystrokes.
Moves the tier-based zoom → radius (km) mapping out of
routes/places/index.tsx and into src/lib/place.ts so that the
upcoming POI-search overlay can reuse the same calibration as the
nearby-check-in circle. No behavior change.
A floating search box that sits absolutely on top of a UserFacingMap.
Debounced (250ms) typeahead against /api/places/poi-search, biased
around the current map center and sized to a radius derived from the
current zoom via the shared zoomToRadius helper. The radius is 2x the
nearby-check-in radius so the net covers roughly the visible viewport
instead of the tight "literally around me" circle.

Returns null when MAP_PROVIDER !== "kakao", so OSM/OSS builds get no
overlay at all (and can't render a broken search UI). Surfaces
loading, error, and empty-results states in the dropdown so users
always get visual feedback. Not wired into any route yet — next
commit.
Adds the floating Kakao POI search on the two interactive maps:

  * PlacePicker (organizer's event-create/edit flow) — the primary
    target. On pick, the map pans and zooms to the POI; the existing
    "New location" panel fills with the picked name.
  * /places check-in page — on pick, the check-in dialog opens
    pre-filled with the POI name; the map re-centers on the picked
    coord.

Both sites now track the current zoom so the overlay's search radius
scales with what the user is looking at. The existing overflow-hidden
rounded frame on /places is kept inside a new positioning wrapper so
the overlay's dropdown isn't clipped.
@coderabbitai

coderabbitai Bot commented Apr 17, 2026 •

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR implements a multi-provider map abstraction layer with support for OSM (Leaflet), Kakao Maps, and Google Maps. It introduces environment-driven provider selection, new UserFacingMap component that dispatches to provider adapters, Kakao POI keyword search integration with client overlay and server-side caching, and updates existing map usages across pages and components to use the new abstraction.

Changes

Cohort / File(s) Summary
Configuration & Environment
.env.example, src/server/env.ts, src/components/maps/config.ts
Added environment variables for map provider selection (MAP_PROVIDER, KAKAO_MAP_APP_KEY, KAKAO_MAP_REST_KEY, GOOGLE_MAPS_API_KEY); exposed them via env object; created useMapConfig() hook to fetch /api/map-config with infinite stale time and no automatic retries.
Type Definitions & Utilities
src/components/maps/types.ts, src/components/maps/icon.ts, src/server/services/poi-search/types.ts, src/lib/place.ts
Defined MapProvider, MarkerColor, MapMarker, CircleOverlay, UserFacingMapProps, MapConfig types; added buildMarkerArtwork(), buildSelectedPinArtwork(), and toDataUri() for SVG marker generation; introduced PoiCandidate, PoiSearchParams, PoiSearchError for search results; added zoomToRadius() utility mapping zoom levels to search radii.
Core Map Abstraction & Adapters
src/components/maps/UserFacingMap.tsx, src/components/maps/index.ts, src/components/maps/adapters/LeafletAdapter.tsx, src/components/maps/adapters/KakaoAdapter.tsx, src/components/maps/adapters/GoogleAdapter.tsx
Created UserFacingMap that dispatches to provider-specific adapters based on runtime config; LeafletAdapter wraps LeafletMap; KakaoAdapter implements full Kakao map with marker management, zoom tracking, bounds fitting, geolocation, and resize observation; GoogleAdapter stub throws "not implemented" error.
Leaflet Refactoring
src/components/LeafletMap.tsx
Consolidated marker/type definitions by re-exporting from maps/types; replaced local icon factories (makeIcon, makeSelectedPin) with buildMarkerArtwork() pipeline; simplified props to use UserFacingMapProps; updated GPS marker and icon placement logic.
Search & Place Discovery
src/components/MapSearchOverlay.tsx, src/server/services/poi-search/index.ts, src/server/services/poi-search/kakao.ts, src/server/services/poi-search/types.ts, src/server/controllers/places/poi-search.ts
Added MapSearchOverlay component with debounced keyword search, radius computation from zoom level, dropdown UI, and candidate selection callback; implemented Kakao POI search backend with in-memory 5-minute LRU cache; added API controller to parse query parameters and delegate to search service.
Map Configuration Endpoint
src/server/controllers/map-config/get.ts, src/server-entry.ts
Created /api/map-config endpoint returning provider and provider-specific keys; wired endpoint in server entry; updated CSP header to conditionally allow Kakao/Daum scripts when provider is Kakao.
Page & Component Integration
src/routes/places/index.tsx, src/routes/places/$placeId/index.tsx, src/routes/events/$eventId/index.tsx, src/components/PlacePicker.tsx
Replaced LeafletMap with UserFacingMap across place listing, place detail, and event detail pages; added MapSearchOverlay to places listing for POI search; imported zoomToRadius from utility; updated PlacePicker to track zoom state and integrate search overlay; unified map usage to new abstraction.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant UserFacingMap
    participant useMapConfig
    participant MapConfigAPI as /api/map-config
    participant Adapter as Adapter<br/>(Kakao/Google/OSM)
    
    Client->>UserFacingMap: render with props
    UserFacingMap->>useMapConfig: fetch config
    useMapConfig->>MapConfigAPI: GET /api/map-config
    MapConfigAPI-->>useMapConfig: { provider, keys }
    useMapConfig-->>UserFacingMap: config & loading/error state
    
    alt config loading
        UserFacingMap->>UserFacingMap: render skeleton
    else config error
        UserFacingMap->>UserFacingMap: render error container
    else config ready
        UserFacingMap->>Adapter: dispatch based on provider
        Adapter->>Adapter: initialize & render map
        Adapter-->>Client: interactive map UI
    end
Loading
sequenceDiagram
    participant User
    participant MapSearchOverlay
    participant Client as Browser
    participant SearchAPI as /api/places/poi-search
    participant KakaoService as Kakao Service
    participant KakaoAPI as Kakao Local API
    
    User->>MapSearchOverlay: type query + focus
    MapSearchOverlay->>MapSearchOverlay: debounce 250ms
    MapSearchOverlay->>SearchAPI: GET with q, lat, lng, radius
    SearchAPI->>KakaoService: searchPois(params)
    
    alt cache hit
        KakaoService-->>SearchAPI: cached PoiCandidate[]
    else cache miss
        KakaoService->>KakaoAPI: keyword search request
        KakaoAPI-->>KakaoService: documents
        KakaoService->>KakaoService: normalize & cache
        KakaoService-->>SearchAPI: PoiCandidate[]
    end
    
    SearchAPI-->>MapSearchOverlay: { candidates }
    MapSearchOverlay->>MapSearchOverlay: render dropdown
    User->>MapSearchOverlay: click candidate
    MapSearchOverlay->>Client: onPick(candidate)
    Client->>Client: update form state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐰 Hoppy times for maps so fine,
Providers swapped with zero sign,
Kakao hops, Google glides, Leaflet stays,
One interface lights all the ways! 🗺️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Kakao map provider + POI name search overlay' accurately captures the main changes: introduction of Kakao as a map provider option and a new search overlay component for discovering points of interest.
Linked Issues check ✅ Passed The PR fulfills key requirements from issue #166: implements provider abstraction with MAP_PROVIDER environment variable, provides Kakao adapter alongside OSM default and Google stub, adds place search via MapSearchOverlay and /api/places/poi-search endpoint, and integrates map picker into relevant user-facing flows.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives. Map provider abstraction, Kakao integration, POI search implementation, and UI layer integration are all within scope. No unrelated refactoring or feature additions detected outside the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/kakao-map-providwr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@malkoG
malkoG merged commit 61c6963 into dev Apr 17, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce map picker component with pluggable provider abstraction (OSM / Kakao / Google)

1 participant