-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 라이브 액티비티 기능 추가 #99
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
Merged
Merged
Changes from 10 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
2cc1832
refactor: CLAUDE.md 수정
pooreumjung 0793537
feat: 코스 경로 좌표 마이그레이션 추가
pooreumjung 1b0978f
feat: 라이브 액티비티 코스 조회 API 추가
pooreumjung ef553cc
feat: 활성 트래킹 세션 중복 방지 인덱스 추가
pooreumjung 7757031
fix: 트래킹 세션 중복 생성 예외 처리 추가
pooreumjung deb248e
refactor: 트래킹 통계 조회 로직 정리
pooreumjung dae0ea2
docs: 트래킹 활성 유저 검증 의도 명시
pooreumjung ff60e1f
refactor: 트래킹 세션 응답 null 직렬화 범위 조정
pooreumjung 094c4dc
refactor: 코스 좌표 응답을 polyline 기반으로 변경
pooreumjung 97b4742
fix: 코스 경로 좌표 누락 예외 처리 추가
pooreumjung a096dca
fix: PR review 반영
pooreumjung 6785eac
Merge branch 'develop' into feat/#49-live-activity
pooreumjung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| # CLAUDE.md | ||
|
|
||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
|
|
||
| ## Communication | ||
|
|
||
| - 한국어로 답변한다. | ||
| - "확인해줘", "봐줘", "뭐가 문제야" 같은 표현은 분석만 요청하는 것으로 간주하고 코드를 수정하지 않는다. | ||
| - "수정해줘", "고쳐줘", "반영해줘", "만들어줘", "추가해줘", "삭제해줘" 같은 명시적 요청이 있을 때만 파일을 편집한다. | ||
| - 수정 전에 어떤 파일을 왜 건드릴지 먼저 설명하고, 명시적 승인을 기다린다. | ||
|
|
||
| ## Build & Test Commands | ||
|
|
||
| ```bash | ||
| ./gradlew build # 전체 빌드 | ||
| ./gradlew test # 전체 테스트 | ||
| ./gradlew test --tests <fully.qualified.TestClass> # 단일 클래스 테스트 | ||
| ./gradlew bootRun # 애플리케이션 실행 (local 프로파일) | ||
| ``` | ||
|
|
||
| 로컬 인프라(PostgreSQL, Redis, MinIO)가 없으면 테스트가 실패할 수 있다. 인프라 부재로 인한 실패와 코드 문제로 인한 실패를 명확히 구분해서 보고한다. | ||
|
|
||
| ## Architecture Overview | ||
|
|
||
| ### Package Structure | ||
|
|
||
| ``` | ||
| com.semosan.api | ||
| ├── common/ # 공통 인프라 (응답, 예외, JWT, 설정, FCM, 알림 공통) | ||
| └── domain/ # 도메인별 비즈니스 로직 | ||
| ├── auth/ # JWT 로그인/로그아웃/토큰 재발급, 회원탈퇴 | ||
| ├── oauth/ # 카카오·애플 소셜 로그인 | ||
| ├── user/ # 사용자 프로필, 온보딩, 알림 설정 | ||
| ├── mountain/ # 산 정보, 코스, 좋아요, 지도 검색 (PostGIS) | ||
| ├── hiking/ # 등산 기록 조회 | ||
| ├── tracking/ # 실시간 GPS 트래킹 (WebSocket STOMP + Redis Stream) | ||
| ├── community/ # 자유게시글·기록게시글, 댓글, 좋아요 | ||
| ├── image/ # MinIO presigned URL 발급 | ||
| ├── notification/# FCM 토큰, 알림 엔티티, 이벤트 | ||
| └── review/ # 산 리뷰 | ||
| ``` | ||
|
|
||
| ### Layer Convention | ||
|
|
||
| 각 도메인은 `controller → service → repository` 계층을 따른다. | ||
| Swagger 문서는 컨트롤러와 분리된 `controller/docs/<Name>ControllerDocs` 인터페이스에 작성하고, 컨트롤러가 이 인터페이스를 구현한다. | ||
|
|
||
| DTO는 다음 규칙을 따른다. | ||
| - 요청: `dto/request/` 하위 또는 `dto/` 직하위에 `*Request.java` | ||
| - 응답: `dto/response/` 하위 또는 `dto/` 직하위에 `*Response.java` | ||
| - 서비스 간 전달 커맨드: `dto/command/` 하위에 `*Command.java` | ||
|
|
||
| ### API Response Convention | ||
|
|
||
| 모든 컨트롤러는 `ApiResponse<T>`로 응답을 반환한다. | ||
|
|
||
| ```java | ||
| // 데이터 없는 성공 | ||
| return ApiResponse.success(SuccessStatus.XXX); | ||
|
|
||
| // 데이터 포함 성공 | ||
| return ApiResponse.success(SuccessStatus.XXX, data); | ||
|
|
||
| // 오류 | ||
| throw new GeneralException(ErrorStatus.XXX); | ||
| ``` | ||
|
|
||
| `SuccessStatus`와 `ErrorStatus` 모두 `BaseStatus` 인터페이스를 구현하는 enum이다. | ||
| 새 에러 코드는 `ErrorStatus`에, 성공 코드는 `SuccessStatus`에 추가한다. | ||
|
|
||
| ### Authentication | ||
|
|
||
| - Stateless JWT 방식. `JwtFilter`가 `Authorization: Bearer <token>` 헤더를 검증한다. | ||
| - WebSocket(STOMP) 연결은 HTTP 필터를 통과(`/ws/tracking/**` permitAll)하고, `StompAuthChannelInterceptor`가 STOMP CONNECT 프레임에서 JWT를 검증한다. | ||
| - 퍼블릭 엔드포인트: 스웨거, OAuth 로그인, 토큰 재발급, `/api/auth/test/login` | ||
|
|
||
| ### Real-time GPS Tracking Flow | ||
|
|
||
| 1. 클라이언트가 WebSocket(STOMP)으로 GPS 좌표를 전송 | ||
| 2. `TrackingGpsPublisher`가 Redis Stream(`tracking:gps`)에 publish | ||
| 3. `TrackingStreamConsumer`가 스트림을 consume → 실시간 통계(Redis Hash) 갱신 + 메모리 버퍼 적재 | ||
| 4. 버퍼가 100개 이상이거나 10초 주기 스케줄러가 동작하면 `TrackingPointFlushService`가 DB에 배치 insert | ||
| 5. 세션 종료 시 `TrackingSessionTerminatedEvent`로 잔여 버퍼를 final flush | ||
|
|
||
| `TrackingPointFlushService`는 self-invocation AOP 우회를 위해 별도 빈으로 분리되어 있다. | ||
|
|
||
| ### Database & Migrations | ||
|
|
||
| - PostgreSQL + PostGIS (지리 좌표 검색에 `hibernate-spatial` + JTS 사용) | ||
| - **prod 프로파일**: Flyway 활성화, `ddl-auto: validate` | ||
| - **local 프로파일**: Flyway 비활성화, `ddl-auto: update` | ||
| - 마이그레이션 스크립트: `src/main/resources/db/migration/V{N}__description.sql` | ||
|
|
||
| ### Infrastructure | ||
|
|
||
| | 컴포넌트 | 용도 | | ||
| |---------|------| | ||
| | PostgreSQL | 메인 DB | | ||
| | Redis | JWT refresh token 블랙리스트, GPS 실시간 통계 (Hash), GPS 이벤트 스트림 (Stream) | | ||
| | MinIO | 이미지 오브젝트 스토리지 (presigned URL 방식) | | ||
| | Firebase FCM | 푸시 알림 | | ||
|
|
||
| ### Environment Variables | ||
|
|
||
| `application.yaml`은 환경변수만 참조한다. 로컬 개발은 `application-local.yaml`에 기본값이 하드코딩되어 있다(로컬 전용). | ||
| prod에서 필요한 주요 변수: `DB_URL`, `DB_USERNAME`, `DB_PASSWORD`, `REDIS_HOST`, `REDIS_PORT`, `JWT_SECRET`, `JWT_ACCESS_TOKEN_EXPIRATION`, `JWT_REFRESH_TOKEN_EXPIRATION`, `KAKAO_CLIENT_ID`, `KAKAO_CLIENT_SECRET`, `KAKAO_REDIRECT_URI`, `KAKAO_ADMIN_KEY`, `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_PUBLIC_URL`, `TRACKING_STREAM_KEY`, `TRACKING_CONSUMER_GROUP`, `FIREBASE_SERVICE_ACCOUNT_PATH`, `DISCORD_ALERT_ENABLED`, `DISCORD_WEBHOOK_URL` | ||
|
|
||
| ## Branch & Commit Convention | ||
|
|
||
| 브랜치명: | ||
| ``` | ||
| feat/#이슈번호-기능명 | ||
| fix/#이슈번호-버그명 | ||
| ``` | ||
|
|
||
| 커밋 타입: `feat`, `fix`, `refactor`, `docs`, `test`, `chore` | ||
|
|
||
| ## PR Rules | ||
|
|
||
| - 리뷰어 2명 전원 승인 후 머지 (셀프 머지 금지) | ||
|
|
||
| ## Swagger | ||
|
|
||
| 로컬 실행 후 `http://localhost:8080/swagger-ui.html` 에서 API 문서 확인 가능. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
src/main/java/com/semosan/api/domain/tracking/dto/response/LiveActivityCourseResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package com.semosan.api.domain.tracking.dto.response; | ||
|
|
||
| import com.semosan.api.common.exception.GeneralException; | ||
| import com.semosan.api.common.status.ErrorStatus; | ||
| import com.semosan.api.domain.mountain.entity.Course; | ||
| import org.locationtech.jts.geom.Coordinate; | ||
| import org.locationtech.jts.geom.LineString; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
|
|
||
| public record LiveActivityCourseResponse( | ||
| Long courseId, | ||
| List<CoordinateInfo> coordinates, | ||
| Double totalDistance, | ||
| Integer estimatedTime | ||
| ) { | ||
|
|
||
| public static LiveActivityCourseResponse from(Course course) { | ||
| return new LiveActivityCourseResponse( | ||
| course.getId(), | ||
| toCoordinates(course.getPolyline()), | ||
| course.getDistance(), | ||
| course.getDuration() | ||
| ); | ||
| } | ||
|
|
||
| private static List<CoordinateInfo> toCoordinates(LineString polyline) { | ||
| if (polyline == null || polyline.isEmpty()) { | ||
| throw new GeneralException(ErrorStatus.TRACKING_COURSE_POLYLINE_REQUIRED); | ||
| } | ||
| return Arrays.stream(polyline.getCoordinates()) | ||
| .map(CoordinateInfo::from) | ||
| .toList(); | ||
| } | ||
|
|
||
| public record CoordinateInfo( | ||
| Double latitude, | ||
| Double longitude | ||
| ) { | ||
| public static CoordinateInfo from(Coordinate coordinate) { | ||
| return new CoordinateInfo( | ||
| coordinate.y, | ||
| coordinate.x | ||
| ); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
src/main/resources/db/migration/V16__add_tracking_session_active_unique_index.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| -- 유저당 활성 트래킹 세션(IN_PROGRESS/PAUSED)은 1개만 허용한다. | ||
| -- 애플리케이션의 exists -> save 검증만으로는 동시 생성 요청을 막을 수 없어서 DB 제약으로 보강한다. | ||
| CREATE UNIQUE INDEX IF NOT EXISTS uq_tracking_sessions_user_active | ||
| ON tracking_sessions (user_id) | ||
| WHERE status IN ('IN_PROGRESS', 'PAUSED'); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.