Skip to content

feat(S15P11A705-397): 나의 활동 기록 집계 조회 API를 추가한다 - #207

Merged
minyongP merged 1 commit into
devfrom
feat/S15P11A705-397-me-activity
Aug 7, 2026
Merged

feat(S15P11A705-397): 나의 활동 기록 집계 조회 API를 추가한다#207
minyongP merged 1 commit into
devfrom
feat/S15P11A705-397-me-activity

Conversation

@minyongP

@minyongP minyongP commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

무엇을

GET /v1/me/activity 하나를 더합니다. 내 기록을 집계해 활동 기록 화면에 한 번에 내려줍니다.

덩어리 내용
totals 기록한 장소 수 · 자치구 수 · 첫 기록일
months 첫 기록 달부터 이번 달까지, 빈 달은 0으로 채움
areas 주소 시·구 기준 상위 5
counts 맥락 메모 · 컬렉션 · 기록한 달 수
highlights 처음 기록한 곳 · 가장 최근 · 가장 붐빈 하루

기간은 전체 누적 고정이며 기간 파라미터를 두지 않습니다. 새 테이블·인덱스·마이그레이션 없습니다.

명세: Team-PinLog/docs#54 (머지됨, §3.7)

판단한 것

날짜 경계를 전부 KST로 고정했습니다. created_atTIMESTAMPTZdate_trunc를 그냥 쓰면 서버 타임존을 탑니다. KST 5/18 00:30 = UTC 5/17 15:30이라 "가장 붐빈 하루"가 하루씩 밀립니다. 그 경계에 걸친 시각을 넣은 테스트(dayBoundaryFollowsSeoulTime)로 고정했습니다 — UTC로 끊는 구현이면 실패합니다.

빈 달 채우기를 SQL이 아니라 서비스에서 합니다. 구간의 끝이 "이번 달"인데 generate_series로 채우면 DB 서버의 시계와 타임존이 응답을 좌우합니다. SQL은 기록이 있는 달만 주고 YearMonth.now(KST)가 채웁니다.

areas 동점 정렬에 COLLATE "C"를 명시했습니다. 한글 이름을 DB 기본 collation으로 정렬하면 운영 DB와 테스트 컨테이너의 locale이 달라 순서가 갈립니다 — S15P11A705-388이 같은 함정을 만나 그때는 이름 정렬 자체를 피했습니다(keywordId로 끊음). 여기서는 파생 문자열이라 끊을 키가 이름뿐이라, 피하는 대신 collation을 박아 고정했습니다.

@SQLRestriction이 걸리지 않는 경로입니다. 엔티티를 거치지 않는 SQL이라 JPA 리포지토리와 반대로 deleted_at IS NULL을 직접 적었습니다. 맥락 메모·컬렉션 카운트는 JPA 파생 쿼리라 그쪽은 적지 않았습니다.

집계를 한 쿼리로 묶지 않았습니다. 서로 다른 테이블의 독립 집계라 조인이 카운트를 곱합니다. MemberSummaryService가 같은 이유로 이미 카운트 넷을 따로 내고 있고, 진입당 1회 호출 경로에서 인덱스를 타는 집계 여섯은 그 복잡도를 살 이유가 되지 않습니다. 대신 대상 집합 정의는 MY_RECORDS CTE 한 곳에 뒀습니다.

범위 밖

  • AI 키워드ai 스키마 소관이라 쓰지 않습니다(4.3을 씁니다)
  • 장소 분류core.place에 category 컬럼이 없습니다
  • 지도 — 화면에서 빼기로 해 좌표를 응답에 싣지 않습니다

알려진 한계

areas의 시·구는 place.address를 공백으로 끊은 두 번째 조각입니다. 행정구역 컬럼이 없어서인데, 도로명·지번이 섞이거나 형식을 벗어난 주소에서는 정확하지 않고 조각을 못 뽑는 주소는 집계에서 제외됩니다. 명세 §3.7에도 같은 내용을 적었습니다.

검증

./gradlew clean check --no-daemon 통과 — 698 테스트 0 실패 0 에러(checkstyle·jacoco 포함). dev 리베이스 후 다시 돌린 결과입니다. 새 테스트 13개는 MeActivityApiTests에 있고, 구현 전 red(12 failed)도 확인했습니다.

🤖 Generated with Claude Code

GET /v1/me/activity 하나로 totals·months·areas·counts·highlights 다섯 덩어리를
내려준다. 기간은 전체 누적 고정이며 기간 파라미터를 두지 않는다. 새 테이블·인덱스·
마이그레이션은 없다.

날짜 경계를 전부 KST로 고정했다. created_at이 TIMESTAMPTZ라 date_trunc를 그냥
쓰면 서버 타임존을 타고, KST 자정 직후에 남긴 기록이 전날에 붙어 "가장 붐빈 하루"가
하루씩 밀린다. 경계에 걸친 시각을 넣은 테스트로 고정했다.

빈 달 채우기는 SQL이 아니라 서비스가 한다. 구간의 끝인 "이번 달"을 SQL에서 정하면
DB 서버의 시계가 응답을 좌우한다.

areas의 동점 정렬에 COLLATE "C"를 명시했다. 한글 이름을 DB 기본 collation으로
정렬하면 운영 DB와 테스트 컨테이너의 locale이 달라 순서가 갈린다(388에서 같은
함정을 만나 그때는 이름 정렬 자체를 피했다).

엔티티를 거치지 않는 SQL이라 @SQLRestriction이 걸리지 않는다 — JPA 리포지토리와
반대로 deleted_at IS NULL을 직접 적었다.

명세: Team-PinLog/docs#54

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@minyongP minyongP added the enhancement 새 기능·개선 (feat) label Aug 7, 2026
@minyongP minyongP self-assigned this Aug 7, 2026
@minyongP
minyongP merged commit ba94ae8 into dev Aug 7, 2026
2 checks passed
@minyongP
minyongP deleted the feat/S15P11A705-397-me-activity branch August 7, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement 새 기능·개선 (feat)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant