Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions .claude/skills/review-code-against-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,36 @@ description: Use when the user asks to create, open, prepare, or publish a PR/pu

## Checklist

### Step 0. diff 수집
### Step 0. 기준 브랜치 결정 및 diff 수집

먼저 `CONTRIBUTING.md`의 Branch Strategy를 확인하고 PR 기준 브랜치를 결정한다.

기준 브랜치 우선순위:

1. 사용자가 PR 대상 브랜치를 명시했으면 그 값을 사용한다.
2. 이미 열린 PR이 있으면 `gh pr view --json baseRefName`의 `baseRefName`을 사용한다.
3. 현재 브랜치가 `feature/*`이면 `dev`를 사용한다.
4. 현재 브랜치가 `hotfix/*`이면 `main`을 사용한다.
5. 현재 브랜치가 `dev`이면 `main`을 사용한다.
6. 현재 브랜치가 `main`이면 `dev`를 사용한다. 이 경우는 hotfix 반영 후 `main` → `dev` 백머지 PR이다.
7. 판단할 수 없으면 사용자에게 PR 대상 브랜치를 확인한다.

브랜치 전략:

| 현재 브랜치 | PR 대상 |
|------|------|
| `feature/*` | `dev` |
| `hotfix/*` | `main` |
| `dev` | `main` |
| `main` | `dev` 백머지 |

```bash
git diff main --name-only
git fetch origin <base-branch> --quiet
git diff --name-only origin/<base-branch>...HEAD
```

보고서에는 기준 브랜치, 현재 브랜치, diff 범위를 먼저 적는다.

변경된 `.java` 파일 목록을 추출한다. 변경 파일이 없으면 "변경 없음"으로 종료한다.

변경된 파일을 두 그룹으로 분류한다:
Expand Down
7 changes: 6 additions & 1 deletion .env.db.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# PostgreSQL
# PostgreSQL (AWS Lightsail Managed DB)
DB_HOST=change-this-lightsail-db-endpoint
DB_NAME=uttae
DB_USER=prod
DB_PASSWORD=change-this-db-password
Expand All @@ -8,6 +9,10 @@ MONGO_USER=prod
MONGO_PASSWORD=change-this-mongo-password
MONGO_DB=uttae

# Redis
REDIS_PASSWORD=change-this-redis-password
REDIS_MAXMEMORY=384mb

# Private network bindings
DB_PRIVATE_IP=10.0.0.13

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ jobs:
grep '^APP_IMAGE=' "$DEPLOY_PATH/.env.prod"
grep '^API_PRIVATE_IP=' "$DEPLOY_PATH/.env.prod"
grep '^LOKI_PUSH_URL=' "$DEPLOY_PATH/.env.prod"
grep '^REDIS_PASSWORD=.' "$DEPLOY_PATH/.env.prod"
docker compose --env-file "$DEPLOY_PATH/.env.prod" -f "$DEPLOY_PATH/compose.app.prod.yaml" config >/dev/null

- name: Detect runtime config changes
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/deploy-db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,23 @@ jobs:
grep '^POSTGRES_EXPORTER_PORT=' .env.db
grep '^REDIS_EXPORTER_PORT=' .env.db
grep '^MONGODB_EXPORTER_PORT=' .env.db
grep '^REDIS_PASSWORD=.' .env.db

docker compose --env-file .env.db -f compose.db.prod.yaml config >/dev/null

- name: Deploy DB services and exporters
run: |
cd "$DEPLOY_PATH"
docker compose --env-file .env.db -f compose.db.prod.yaml pull postgres redis mongodb postgres-exporter redis-exporter mongodb-exporter node-exporter
docker compose --env-file .env.db -f compose.db.prod.yaml up -d --wait --wait-timeout "$DB_WAIT_TIMEOUT_SECONDS" postgres redis mongodb postgres-exporter redis-exporter mongodb-exporter node-exporter
docker compose --env-file .env.db -f compose.db.prod.yaml pull postgres-exporter redis mongodb redis-exporter mongodb-exporter node-exporter
docker compose --env-file .env.db -f compose.db.prod.yaml up -d --wait --wait-timeout "$DB_WAIT_TIMEOUT_SECONDS" postgres-exporter redis mongodb redis-exporter mongodb-exporter node-exporter
docker compose --env-file .env.db -f compose.db.prod.yaml ps

- name: Print DB logs on deploy failure
if: failure()
run: |
cd "$DEPLOY_PATH"
docker compose --env-file .env.db -f compose.db.prod.yaml ps
docker compose --env-file .env.db -f compose.db.prod.yaml logs postgres redis mongodb postgres-exporter redis-exporter mongodb-exporter node-exporter --tail=200
docker compose --env-file .env.db -f compose.db.prod.yaml logs postgres-exporter redis mongodb redis-exporter mongodb-exporter node-exporter --tail=200

- name: Prune unused Docker images
if: always()
Expand Down
11 changes: 6 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
## Tech Stack

- **Framework**: Spring Boot 4.0.5, Java 21
- **Database**: PostgreSQL 17 + PostGIS 3.5, MongoDB 8
- **Database**: PostgreSQL 17.10, MongoDB 8.0.21
- **Cache**: Redis 8
- **Auth**: Spring Security
- **Realtime**: WebSocket + STOMP
- **Build**: Gradle
- **기타**: Lombok, Spring Data JPA, Spring Data MongoDB, `hibernate-spatial`
- **기타**: Lombok, Spring Data JPA, Spring Data MongoDB

## Commands

Expand Down Expand Up @@ -78,9 +78,10 @@ src/main/resources/

## Gotchas

- PostgreSQL 이미지는 `postgis/postgis:17-3.5`를 사용한다.
- MongoDB 이미지는 `mongo:8`을 사용하며 채팅 메시지 저장소로 사용한다.
- 공간 데이터 엔티티에는 `hibernate-spatial` 타입을 사용한다.
- PostgreSQL 이미지는 `postgres:17.10`을 사용한다.
- MongoDB 이미지는 `mongo:8.0.21`을 사용하며 채팅 메시지 저장소로 사용한다.
- PostgreSQL 스키마 변경은 Flyway SQL 마이그레이션으로 관리한다.
- MongoDB 컬렉션/인덱스 변경은 Mongock ChangeUnit으로 관리한다. 변경 클래스는 `common/migration/mongo/` 아래에 두고, Spring Data 자동 인덱스 생성에 의존하지 않는다.
- dev 환경의 PostgreSQL 포트는 `5433`이다.
- `spring.jpa.open-in-view=false`가 설정되어 있다.
- WebSocket + STOMP 사용 시 Spring Security 설정에서 WebSocket 엔드포인트를 별도 허용해야 한다.
Expand Down
5 changes: 4 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ feature/<topic> → dev → main
- `main`에 직접 push하지 않는다.
- `feature/*` 브랜치는 `dev`에서 분기하고 `dev`로 머지한다.
- 브랜치는 작업 단위로 짧게 유지한다.
- `feature/*` 브랜치의 PR 대상은 `dev`다.
- `dev` 브랜치의 릴리스 PR 대상은 `main`이다.

### Hotfix 흐름

Expand All @@ -28,8 +30,9 @@ hotfix/<topic> → main → dev (백머지)
```

- `hotfix/*` 브랜치는 `main`에서 직접 분기한다.
- `hotfix/*` 브랜치의 PR 대상은 `main`이다.
- 수정 완료 후 `main`에 머지한다.
- 머지 후 동일 변경사항을 반드시 `dev`에도 백머지하여 이후 릴리스에서 누락되지 않도록 한다.
- 머지 후 `main`에서 `dev`로 백머지 PR을 올려 동일 변경사항이 이후 릴리스에서 누락되지 않도록 한다.

## Commit Convention

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

- **Language**: Java 21
- **Framework**: Spring Boot 4.0.5
- **Database**: PostgreSQL 17 + PostGIS 3.5, MongoDB 8
- **Database**: PostgreSQL 17.10, MongoDB 8.0.21
- **Cache**: Redis 8
- **Auth**: Spring Security + JWT
- **Realtime**: WebSocket + STOMP
Expand All @@ -29,7 +29,7 @@ src/main/java/com/howaboutus/backend/
├── auth/ ← 인증/인가
├── bookmarks/ ← 북마크
├── messages/ ← 채팅 메시지 (MongoDB)
├── places/ ← 장소 (PostGIS 공간 데이터)
├── places/ ← 장소 (Google Places API 연동)
├── realtime/ ← WebSocket 실시간 통신
├── rooms/ ← 채팅방
├── schedules/ ← 일정
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.hibernate.orm:hibernate-spatial'
runtimeOnly 'org.postgresql:postgresql'
implementation 'org.springframework.boot:spring-boot-starter-flyway'
runtimeOnly 'org.flywaydb:flyway-database-postgresql'
implementation 'io.mongock:mongock-standalone:5.5.1'
implementation 'io.mongock:mongodb-sync-v4-driver:5.5.1'

implementation 'org.springframework.boot:spring-boot-starter-security'

Expand Down
7 changes: 3 additions & 4 deletions compose.db.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ name: uttae

services:
postgres:
image: 'postgis/postgis:17-3.5'
platform: linux/amd64
image: 'postgres:17.10'
env_file:
- ./.env.dev
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
test: ["CMD-SHELL", "pg_isready -h localhost -p 5432 -U $${DB_USER} -d $${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 10
Expand All @@ -35,7 +34,7 @@ services:
- redis-data:/data

mongodb:
image: 'mongo:8'
image: 'mongo:8.0.21'
env_file:
- ./.env.dev
environment:
Expand Down
59 changes: 22 additions & 37 deletions compose.db.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,26 @@
name: uttae-data

services:
postgres:
image: 'postgis/postgis:17-3.5'
env_file:
- .env.db
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 10
ports:
- '${DB_PRIVATE_IP}:5432:5432'
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1.0'
memory: 2g
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"

redis:
image: 'redis:8.6.2'
command: ["redis-server", "--notify-keyspace-events", "Ex"]
env_file:
- .env.db
command:
- redis-server
- --notify-keyspace-events
- Ex
- --save
- ""
- --appendonly
- "no"
- --maxmemory
- ${REDIS_MAXMEMORY:-384mb}
- --maxmemory-policy
- noeviction
- --requirepass
- ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
test: ["CMD-SHELL", "redis-cli --no-auth-warning -a \"$${REDIS_PASSWORD}\" ping"]
interval: 10s
timeout: 5s
retries: 10
Expand All @@ -56,7 +42,7 @@ services:
max-file: "3"

mongodb:
image: 'mongo:8'
image: 'mongo:8.0.21'
env_file:
- .env.db
environment:
Expand Down Expand Up @@ -87,14 +73,11 @@ services:
postgres-exporter:
image: prometheuscommunity/postgres-exporter:v0.19.1
environment:
DATA_SOURCE_URI: postgres:5432/${DB_NAME}?sslmode=disable
DATA_SOURCE_URI: ${DB_HOST}:5432/${DB_NAME}?sslmode=require
DATA_SOURCE_USER: ${DB_USER}
DATA_SOURCE_PASS: ${DB_PASSWORD}
ports:
- '${DB_PRIVATE_IP}:${POSTGRES_EXPORTER_PORT}:9187'
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
logging:
driver: "json-file"
Expand All @@ -104,8 +87,11 @@ services:

redis-exporter:
image: oliver006/redis_exporter:v1.83.0
env_file:
- .env.db
environment:
REDIS_ADDR: redis://redis:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
ports:
- '${DB_PRIVATE_IP}:${REDIS_EXPORTER_PORT}:9121'
depends_on:
Expand Down Expand Up @@ -156,6 +142,5 @@ services:
max-file: "3"

volumes:
postgres-data:
redis-data:
mongodb-data:
6 changes: 3 additions & 3 deletions docs/ai/erd.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **초안 문서입니다.** 구현 과정에서 컬럼 및 관계가 변경될 수 있습니다.

- **기술 스택:** PostgreSQL + PostGIS, MongoDB (채팅 메시지), Redis (세션/토큰)
- **기술 스택:** PostgreSQL, MongoDB (채팅 메시지), Redis (세션/토큰)
- **ID 전략:** roomId → UUID (초대 URL 보안), MongoDB message `_id` → 읽음 위치 복귀/히스토리 cursor, PostgreSQL 엔티티 → BIGINT auto-increment

---
Expand Down Expand Up @@ -101,9 +101,9 @@ CHECK `ck_room_members_left_role` (status='ACTIVE' OR role <> 'PENDING')
| metadata | DOCUMENT | NOT NULL, DEFAULT `{}` | 장소 공유, AI 응답 등 메시지 타입별 확장 데이터 |
| createdAt | DATE | NOT NULL | 생성일시 |

**인덱스:** `{ roomId: 1, sequence: 1 }` unique — 방별 sequence 중복 방지 및 `afterSequence` 조회용, `{ roomId: 1, sequence: -1, _id: -1 }` — 최근 메시지 조회용, `{ roomId: 1, createdAt: -1, _id: -1 }` — 기존 최근 조회/운영 호환용, `{ roomId: 1, _id: 1 }` — `afterId` 호환 조회용
**인덱스:** `uidx_messages_room_sequence` `{ roomId: 1, sequence: 1 }` unique — 방별 sequence 중복 방지 및 `afterSequence` 조회용, `idx_messages_room_sequence_recent` `{ roomId: 1, sequence: -1, _id: -1 }` — 최근 메시지 조회용, `idx_messages_room_recent` `{ roomId: 1, createdAt: -1, _id: -1 }` — 기존 최근 조회/운영 호환용, `idx_messages_room_id` `{ roomId: 1, _id: 1 }` — `afterId` 호환 조회용

> 운영 MongoDB 기존 메시지는 서버 배포 전 `scripts/mongo/backfill-message-sequence.js`로 room별 `_id` 오름차순 기준 `sequence: 1, 2, 3...`을 채운 뒤 missing/null 및 중복 검증을 통과해야 한다. `(roomId, sequence)` unique index는 backfill 검증 완료 후 스크립트에서 생성한다.
> 초기 빈 MongoDB 배포에서는 Mongock 마이그레이션이 `messages` 컬렉션과 위 인덱스를 생성한다. 운영 MongoDB 기존 메시지가 있는 환경은 서버 배포 전 room별 `_id` 오름차순 기준 `sequence: 1, 2, 3...` backfill과 missing/null 및 중복 검증을 별도 절차로 완료해야 한다. `(roomId, sequence)` unique index는 backfill 검증 완료 후 적용한다.
>
> 채팅 초기 진입/재접속에서 저장된 읽음 위치로 복귀할 때는 `room_members.last_read_message_id`를 `afterId`로 보낸다. 실시간 수신 중 브로드캐스트 누락이 감지되면 마지막 수신 message `sequence`를 `afterSequence`로 보내 복구한다.

Expand Down
Loading