-
Notifications
You must be signed in to change notification settings - Fork 0
docs: .claude 문서 세팅 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| # API.md — REST API 계약 및 컨트롤러 | ||
|
|
||
| ## 목표 (Goal) | ||
|
|
||
| 외부 클라이언트에 노출되는 검색·라우팅·관리자 REST API의 계약(URI, 파라미터, 응답 스키마)을 확정하고, 그에 대응하는 컨트롤러·DTO·예외 처리를 구현한다. 1차 프로토타입은 **인증 없이** 모든 엔드포인트를 노출한다(운영 전 Spring Security 도입 필수). | ||
|
|
||
| ## 선행조건 (Prerequisites) | ||
|
|
||
| - [ROUTING.md](ROUTING.md) 완료: `RouteService.route(...)` 호출 가능. | ||
| - [SEARCH.md](SEARCH.md) 완료: `SearchService.autocomplete(...)`, `findById(...)` 호출 가능. | ||
| - [DATA_MODEL.md](DATA_MODEL.md) 완료: 도메인 엔티티 CRUD 가능. | ||
|
|
||
| ## 변경/생성 파일 (Files to Change) | ||
|
|
||
| | 경로 | 종류 | 역할 | | ||
| |------|------|------| | ||
| | `../src/main/java/com/honggwart/map/search/api/SearchController.java` | 신규 | 자동완성·상세 조회 | | ||
| | `../src/main/java/com/honggwart/map/search/api/dto/*` | 신규 | 응답 DTO (SEARCH.md와 공유) | | ||
| | `../src/main/java/com/honggwart/map/routing/api/RouteController.java` | 신규 | 길찾기 | | ||
| | `../src/main/java/com/honggwart/map/routing/api/dto/*` | 신규 | RouteResponse, StepDto, PolylinePointDto 등 | | ||
| | `../src/main/java/com/honggwart/map/admin/api/AdminController.java` | 신규 | Place·Node·Edge CRUD + reload/reindex | | ||
| | `../src/main/java/com/honggwart/map/admin/api/dto/*` | 신규 | 관리자 요청·응답 DTO | | ||
| | `../src/main/java/com/honggwart/map/common/web/ApiExceptionHandler.java` | 신규 | `@RestControllerAdvice`로 400/404 매핑 | | ||
|
|
||
| ## 구현 (Implementation) | ||
|
|
||
| ### §1. 검색 API | ||
|
|
||
| #### `GET /api/search/autocomplete` | ||
|
|
||
| ``` | ||
| GET /api/search/autocomplete?q=T&limit=10 | ||
| → 200 | ||
| { | ||
| "suggestions": [ | ||
| { "placeId": 101, "displayName": "공학관 501호", "kind": "ROOM", "category": "OFFICE" }, | ||
| { "placeId": 1, "displayName": "공학관", "kind": "BUILDING" } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| - 파라미터: `q`(required, 1자 이상), `limit`(optional, 기본 10, 최대 50). | ||
| - 빈 q는 400. | ||
| - 응답 단위는 `place_id`. ES 쿼리 후 중복 제거하고 weight 순 정렬은 [SEARCH.md](SEARCH.md) §5에서 처리. | ||
| - 입력 정규화(NFC, 소문자, 공백 단일화)는 ES analyzer가 담당. 컨트롤러는 trim만. | ||
|
|
||
| #### `GET /api/places/{placeId}` | ||
|
|
||
| ``` | ||
| GET /api/places/101 | ||
| → 200 | ||
| { | ||
| "placeId": 101, | ||
| "kind": "ROOM", | ||
| "displayName": "공학관 501호", | ||
| "category": "OFFICE", | ||
| "parentPlace": { "placeId": 1, "displayName": "공학관" }, | ||
| "entrances": [ | ||
| { "label": "정문", "nodeId": 551, "isPrimary": true }, | ||
| { "label": "후문", "nodeId": 552, "isPrimary": false } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| - 미존재 placeId는 404. | ||
| - 데이터 원천은 PostgreSQL (`SearchService.findById`). | ||
|
|
||
| ### §2. 라우팅 API | ||
|
|
||
| #### `GET /api/route` | ||
|
|
||
| ``` | ||
| GET /api/route?fromPlaceId=101&toPlaceId=250&mode=INDOOR_PREFERRED | ||
| → 200 | ||
| { | ||
| "summary": { | ||
| "distanceMeters": 320.5, | ||
| "estimatedSeconds": 280, | ||
| "floorChanges": 1, | ||
| "mode": "INDOOR_PREFERRED" | ||
| }, | ||
| "from": { "placeId": 101, "entranceLabel": "정문", "nodeId": 551 }, | ||
| "to": { "placeId": 250, "entranceLabel": "후문", "nodeId": 902 }, | ||
| "polyline": [ | ||
| { "lat": 37.0001, "lng": 127.0001, "floor": 1, "indoor": false } | ||
| ], | ||
| "steps": [ | ||
| { "type": "DEPART", "instruction": "공학관 정문에서 출발합니다", "polylineRange": [0, 0] }, | ||
| { "type": "TURN_LEFT", "instruction": "좌회전 후 직진하세요", "polylineRange": [0, 12] }, | ||
| { "type": "ENTER_BUILDING", "instruction": "공학관 안으로 들어가세요", "polylineRange": [12, 13] }, | ||
| { "type": "FLOOR_CHANGE", "instruction": "엘리베이터로 5층까지 올라가세요", | ||
| "meta": { "fromFloor": 1, "toFloor": 5, "transport": "ELEVATOR" }, | ||
| "polylineRange": [13, 14] }, | ||
| { "type": "ARRIVE", "instruction": "공학관 501호에 도착했습니다", "polylineRange": [14, 14] } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| **파라미터** | ||
|
|
||
| - `fromPlaceId` (long, required) | ||
| - `toPlaceId` (long, required) | ||
| - `mode` (enum, optional, 기본 `SHORTEST`) — `SHORTEST` | `INDOOR_PREFERRED` | `STAIR_AVOIDANCE` | ||
|
|
||
| **필드 의미** | ||
|
|
||
| - `polyline`: 지도 렌더링용 전체 노드열. 곡선이면 모든 점이 포함됨. | ||
| - `steps[*].polylineRange = [startIndex, endIndex]`: 해당 step이 덮는 polyline 인덱스 구간. 곡선은 단일 step의 range 안에 흡수됨. | ||
| - `from.entranceLabel` / `to.entranceLabel`: 다중 입구 중 실제 사용된 입구의 label (RouteService가 [ROUTING.md](ROUTING.md) §4 다중 출발/도착 Dijkstra로 자동 선택). | ||
|
|
||
| **StepType enum** | ||
|
|
||
| `DEPART | TURN_LEFT | TURN_RIGHT | ENTER_BUILDING | EXIT_BUILDING | FLOOR_CHANGE | ARRIVE` | ||
|
|
||
| **FLOOR_CHANGE.meta** | ||
|
|
||
| ```json | ||
| { "fromFloor": 1, "toFloor": 5, "transport": "ELEVATOR" } | ||
| ``` | ||
|
|
||
| `transport`는 `STAIR` | `ELEVATOR` (해당 구간 Edge.edge_type에서 도출). | ||
|
|
||
| **에러** | ||
|
|
||
| - 미존재 placeId, 경로 없음(연결 안 됨) → 404 + 에러 메시지. | ||
| - 잘못된 mode → 400. | ||
|
|
||
| ### §3. 관리자 API (1차: 인증 없음) | ||
|
|
||
| `/api/admin/**` 하위. **인증 미적용** — 1차 프로토타입 한정. 운영 전 Spring Security 도입 필수. | ||
|
Comment on lines
+128
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 무인증 관리자 API는 운영 환경 강제 차단 규칙이 필요합니다. “운영 전 도입 필수” 문구만으로는 배포 실수를 막지 못합니다. Also applies to: 168-168 🤖 Prompt for AI Agents |
||
|
|
||
| #### Place CRUD | ||
|
|
||
| ``` | ||
| POST /api/admin/places # Place 생성 | ||
| PUT /api/admin/places/{id} | ||
| DELETE /api/admin/places/{id} | ||
| POST /api/admin/places/{id}/aliases | ||
| DELETE /api/admin/places/{id}/aliases/{aliasId} | ||
| POST /api/admin/places/{id}/entrances # PlaceEntrance 등록 | ||
| DELETE /api/admin/places/{id}/entrances/{entranceId} | ||
| ``` | ||
|
|
||
| #### Node/Edge CRUD | ||
|
|
||
| ``` | ||
| POST /api/admin/nodes | ||
| PUT /api/admin/nodes/{id} | ||
| DELETE /api/admin/nodes/{id} | ||
| POST /api/admin/edges | ||
| PUT /api/admin/edges/{id} | ||
| DELETE /api/admin/edges/{id} | ||
| ``` | ||
|
|
||
| #### 운영 작업 | ||
|
|
||
| ``` | ||
| POST /api/admin/graph/reload # GraphRegistry 수동 재빌드 | ||
| POST /api/admin/index/reindex # ES 전체 재색인 | ||
| ``` | ||
|
|
||
| #### 동기화 트리거 | ||
|
|
||
| - Place·PlaceAlias·PlaceEntrance 변경 트랜잭션 커밋 후 → `PlacePersistedEvent` 발행 → [SEARCH.md](SEARCH.md) `PlaceIndexer`가 수신해 ES 색인 갱신. | ||
| - Node·Edge 변경 트랜잭션 커밋 후 → `GraphChangedEvent` 발행 → [ROUTING.md](ROUTING.md) `GraphRegistry.reload()` 호출. | ||
| - 이벤트 발행 책임은 `AdminController`(또는 그가 호출하는 서비스 계층)에 있다. | ||
|
|
||
| **경고**: 이 영역의 모든 엔드포인트는 1차 프로토타입 한정 인증 없음. 실제 사용자에게 공개 가능한 환경에서는 반드시 인증 미들웨어를 거쳐야 한다. | ||
|
|
||
| ### §4. 공통 처리 | ||
|
|
||
| - DTO에는 Bean Validation(`@NotNull`, `@Positive` 등)을 적용. | ||
| - `@RestControllerAdvice`로 `MethodArgumentNotValidException` → 400, `EntityNotFoundException` 등 → 404로 매핑. | ||
| - 응답 일관성: 모든 4xx/5xx는 `{ "error": { "code": "...", "message": "..." } }` 형태. | ||
|
Comment on lines
+172
to
+174
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쿼리 파라미터 검증 예외 매핑이 누락되어 400 계약이 깨질 수 있습니다. 현재 매핑 정의만으로는 🤖 Prompt for AI Agents |
||
|
|
||
| ## 검증 (Verification) | ||
|
|
||
| ### MockMvc 테스트 (단위·슬라이스) | ||
| - `SearchControllerTest`: `q` 누락 → 400, 정상 응답 JSON 구조 검증, `limit` 상한 초과 → 400. | ||
| - `RouteControllerTest`: 미존재 placeId → 404, mode 누락 시 기본값 `SHORTEST` 적용. | ||
| - `AdminControllerTest`: Place 생성 후 200/201과 Location 헤더, `entrances` POST 시 PlaceEntrance가 생성되는지. | ||
|
|
||
| ### 통합 테스트 (Testcontainers) | ||
| - `RouteIntegrationTest`: PG+ES 컨테이너 기동 → 시드 → `/api/route?...&mode=INDOOR_PREFERRED` 호출 → polyline·steps 검증. (라우팅 도메인 로직 검증은 [ROUTING.md](ROUTING.md), 본 영역은 HTTP 계약 검증.) | ||
| - `SearchIntegrationTest`: `/api/search/autocomplete?q=T` 호출 → 응답 형식·중복 제거 검증. (검색 도메인 로직 검증은 [SEARCH.md](SEARCH.md).) | ||
| - `AdminFlowTest`: | ||
| - Place 생성 → `/api/places/{id}`로 조회 가능. | ||
| - PlaceEntrance 등록 → 곧바로 `/api/route` 호출 시 새 입구가 사용 가능. | ||
| - Node/Edge 생성 → `/api/admin/graph/reload` 호출 후 그 노드를 경유하는 경로가 라우팅 결과에 등장. | ||
| - `/api/admin/index/reindex` 호출 후 자동완성이 정상 동작. | ||
|
|
||
| ### 수동 검증 (curl 시나리오) | ||
|
|
||
| ```bash | ||
| docker-compose up -d | ||
| ./gradlew bootRun | ||
|
|
||
| # 자동완성 | ||
| curl "http://localhost:8080/api/search/autocomplete?q=공학" | ||
|
|
||
| # 상세 조회 | ||
| curl "http://localhost:8080/api/places/1" | ||
|
|
||
| # 길찾기 (3가지 모드를 같은 from/to로) | ||
| curl "http://localhost:8080/api/route?fromPlaceId=1&toPlaceId=2&mode=SHORTEST" | ||
| curl "http://localhost:8080/api/route?fromPlaceId=1&toPlaceId=2&mode=INDOOR_PREFERRED" | ||
| curl "http://localhost:8080/api/route?fromPlaceId=1&toPlaceId=2&mode=STAIR_AVOIDANCE" | ||
|
|
||
| # 모드별로 summary.distanceMeters와 summary.floorChanges가 달라지는지 확인. | ||
| ``` | ||
|
|
||
| ## 참조 (References) | ||
|
|
||
| - 라우팅 도메인: [ROUTING.md](ROUTING.md) | ||
| - 검색 도메인: [SEARCH.md](SEARCH.md) | ||
| - 엔티티: [DATA_MODEL.md](DATA_MODEL.md) | ||
| - 인프라(애플리케이션 기동): [INFRASTRUCTURE.md](INFRASTRUCTURE.md) | ||
| - 설계 배경: [../DRAFT.md](../DRAFT.md) §4.7, §5.6, §5.7 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| # ARCHITECTURE.md — 시스템 청사진 (참조 전용) | ||
|
|
||
| > 이 파일은 **참조 전용**입니다. 작업 단위(목표·선행조건·변경 파일·구현·검증) 절차는 [PLAN.md](PLAN.md) 인덱스의 각 영역 파일을 참고하세요. | ||
|
|
||
| --- | ||
|
|
||
| ## Context | ||
|
|
||
| 본 시스템은 [DRAFT.md](../DRAFT.md)의 설계 초안을 바탕으로 사용자의 결정사항을 반영한 **Spring Boot 기반 교내 시설 길찾기 백엔드**다. 현재 백엔드는 Spring Boot 4.0.6 / Java 21 환경의 빈 프로젝트(`MapApplication.java`만 존재) 상태이며, DRAFT 대비 다음 핵심 차이를 반영한다. | ||
|
|
||
| - **장소 모델 변경**: `Place.entranceNodeId`(1:1) → `PlaceEntrance` 조인 테이블(1:N). 하나의 장소가 정문/후문/연결통로 등 여러 출입 노드를 가질 수 있다. | ||
| - **Building 엔티티 통합**: 별도 `Building` 테이블 폐기. `Place.kind = BUILDING | ROOM | FACILITY`로 통합하고 `parent_place_id`로 강의실↔건물 관계 표현. 별칭은 `PlaceAlias` 단일 테이블. | ||
| - **검색 모드 추가**: `SHORTEST | INDOOR_PREFERRED | STAIR_AVOIDANCE` 세 모드(단일 선택). ACCESSIBLE(휠체어) 모드는 본 프로젝트에서 다루지 않음. | ||
| - **검색엔진**: Elasticsearch 채택. 단, `SearchService` 인터페이스를 두어 구현체 교체 가능 구조. | ||
| - **회전 지시 규칙**: 실외 경로에서만 회전 step 생성, 실내 회전은 지시에서 제외. | ||
|
|
||
| 목적: DRAFT가 정의한 검색·길찾기 도메인을 구현 가능한 코드 단위로 분해하고, 진행 순서·파일 구조·API 계약·검증 방법을 확정한다. | ||
|
|
||
| --- | ||
|
|
||
| ## 1. 기술 스택 | ||
|
|
||
| | 영역 | 선택 | 비고 | | ||
| |------|------|------| | ||
| | 프레임워크 | Spring Boot 4.0.6 / Java 21 | 현 build.gradle 유지 | | ||
| | ORM | Spring Data JPA + Hibernate | `@JdbcTypeCode(SqlTypes.JSON)`으로 JSONB 매핑 | | ||
| | DB | PostgreSQL | JSONB 컬럼 사용 | | ||
| | 마이그레이션 | Flyway | `src/main/resources/db/migration/` | | ||
| | 좌표계 | `double lat/lng` (PostGIS 미사용) | Haversine으로 거리 계산 | | ||
| | 검색엔진 | Elasticsearch | `SearchService` 인터페이스 + ES 구현체 | | ||
| | 그래프 | 기동 시 인메모리 인접 리스트 적재 | 데이터 변경 시 갱신 | | ||
| | 인증 | **없음 (1차 프로토타입)** | 운영 전 반드시 추가 | | ||
| | 로컬 환경 | docker-compose (PG + ES) | 프로젝트 루트 `docker-compose.yml` | | ||
|
|
||
| 세부 의존성 목록은 [INFRASTRUCTURE.md](INFRASTRUCTURE.md)를 참조한다. | ||
|
|
||
| --- | ||
|
|
||
| ## 2. 패키지 구조 | ||
|
|
||
| ``` | ||
| com.honggwart.map | ||
| ├── MapApplication.java | ||
| ├── config/ | ||
| │ ├── ElasticsearchConfig.java | ||
| │ └── JpaConfig.java | ||
| ├── domain/ | ||
| │ ├── place/ # Place, PlaceAlias, PlaceEntrance, PlaceKind | ||
| │ ├── graph/ # Node, Edge, NodeType, EdgeType | ||
| │ └── common/ # 공통 enum, 값 객체 | ||
| ├── search/ | ||
| │ ├── api/ # SearchController, DTO | ||
| │ ├── service/ # SearchService(인터페이스), ESSearchService | ||
| │ ├── indexer/ # PlaceIndexer (트랜잭션 이벤트 리스너) | ||
| │ └── es/ # ES 도큐먼트, repository | ||
| ├── routing/ | ||
| │ ├── api/ # RouteController, DTO | ||
| │ ├── service/ # RouteService, RoutingMode | ||
| │ ├── graph/ # GraphRegistry(인메모리 그래프), GraphLoader | ||
| │ ├── algorithm/ # Dijkstra, WeightPolicy | ||
| │ └── instruction/ # StepExtractor, BearingCalculator | ||
| └── admin/ | ||
| └── api/ # AdminController (CRUD) | ||
| ``` | ||
|
Comment on lines
+41
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다이어그램 코드 블록에 언어를 지정해 주세요. Line 41, Line 70의 fenced code block에 언어 지정이 없어 markdownlint(MD040) 경고가 납니다. 예를 들어 패키지 트리는 Also applies to: 70-108 🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 41-41: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
|
|
||
| --- | ||
|
|
||
| ## 3. 영역 경계 다이어그램 | ||
|
|
||
| ``` | ||
| ┌──────────────────────┐ | ||
| │ API.md (Controller) │ | ||
| │ ───────────────── │ | ||
| │ SearchController │ | ||
| │ RouteController │ | ||
| │ AdminController │ | ||
| └─────┬───────┬───────┬─┘ | ||
| │ │ │ | ||
| ┌───────▼─┐ ┌─▼──────▼──────┐ | ||
| │SEARCH.md│ │ ROUTING.md │ | ||
| │ ────── │ │ ────────── │ | ||
| │ Service │ │ Service │ | ||
| │ Indexer │ │ GraphRegistry │ | ||
| │ ES Doc │ │ Dijkstra │ | ||
| └──┬──────┘ │ WeightPolicy │ | ||
| │ │ StepExtractor │ | ||
| │ └─────┬─────────┘ | ||
| │ │ | ||
| │ ┌────────▼────────────┐ | ||
| │ │ DATA_MODEL.md │ | ||
| └──────▶│ ─────────────────── │ | ||
| │ Place / Alias / │ | ||
| │ Entrance / Node / │ | ||
| │ Edge (JPA + DDL) │ | ||
| └─────────┬───────────┘ | ||
| │ | ||
| ┌─────────▼───────────┐ | ||
| │ INFRASTRUCTURE.md │ | ||
| │ ─────────────────── │ | ||
| │ build.gradle │ | ||
| │ application.yml │ | ||
| │ docker-compose │ | ||
| │ Flyway V1 │ | ||
| └─────────────────────┘ | ||
|
|
||
| 외부: PostgreSQL ◀───┘ | ||
| Elasticsearch ◀── (SEARCH.md indexer + service) | ||
| ``` | ||
|
|
||
| 핵심 연결 지점: | ||
| - **검색 ↔ 그래프**: `Place ─< PlaceEntrance >─ Node`. 사용자는 Place를 검색·선택하고, 라우팅 서비스가 PlaceEntrance의 다중 node_id 집합을 가상 노드로 묶어 Dijkstra를 수행한다. | ||
| - **검색 색인 동기화**: Place 트랜잭션 커밋 후 `PlacePersistedEvent` → `PlaceIndexer` → ES upsert (동기). | ||
| - **그래프 동기화**: Node/Edge 트랜잭션 커밋 후 `GraphRegistry.reload()` 호출. | ||
|
|
||
| --- | ||
|
|
||
| ## 4. 영역 파일 매핑 | ||
|
|
||
| | 영역 | 책임 | 파일 | | ||
| |------|------|------| | ||
| | 인프라 부트스트랩 | 의존성·환경·DDL 초기화 | [INFRASTRUCTURE.md](INFRASTRUCTURE.md) | | ||
| | 데이터 모델 | DDL·JPA 엔티티·시드 | [DATA_MODEL.md](DATA_MODEL.md) | | ||
| | 라우팅 | 그래프·Dijkstra·step 추출 | [ROUTING.md](ROUTING.md) | | ||
| | 검색 | ES 색인·서비스·동기화 | [SEARCH.md](SEARCH.md) | | ||
| | REST API | 검색·라우팅·관리자 컨트롤러 | [API.md](API.md) | | ||
|
|
||
| 원본 설계 초안은 [DRAFT.md](../DRAFT.md)에 있다. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 블록 언어 태그를 명시해 markdownlint(MD040) 경고를 제거해 주세요.
예: API 예시는
```http, JSON 응답은```json, 엔드포인트 목록은```text로 지정하면 됩니다.Also applies to: 49-63, 72-97, 134-142, 146-153, 157-160
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 31-31: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents