Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
177 commits
Select commit Hold shift + click to select a range
bfb5d77
[Feat] 문제 자동 복습 알림 저장 구조 추가 - Flyway 예약 엔티티 정책 구현
KiSeungMin Jul 18, 2026
4f834b9
[Feat] 문제 망각곡선 알림 발송 배치 추가 - 작성시각 기준 예약 삭제 처리
KiSeungMin Jul 18, 2026
e6e63d1
[Feat] 문제 이미지 URL 수정 API 추가 및 당일 복습 이미지 중복 방지
KiSeungMin Jul 21, 2026
0d90fef
[Feat] 리마인더 발송 배치 개선 - DB 필터링 쿼리 최적화 및 발송 로직 분리
KiSeungMin Jul 21, 2026
68597aa
[Perf] V21 DATETIME(6) 수정 및 V22 누락 인덱스 추가
KiSeungMin Jul 21, 2026
d47ec1d
[Test] 리마인더 통합 테스트 추가 및 기존 테스트 수정
KiSeungMin Jul 21, 2026
8dcc2ad
[Chore] feat.md 커밋 방식 업데이트 - 단위 구현 후 즉시 커밋 진행
KiSeungMin Jul 21, 2026
495b30f
[Feat] 오답노트 망각곡선 기반 복습 알림 시스템 추가
KiSeungMin Jul 21, 2026
1cf96b4
[Fix] soft delete 태그 중복 생성 500 수정 - 유니크 인덱스에 alive_key 추가
KiSeungMin Aug 22, 2026
a81ae47
[Fix] Sentry 4xx 노이즈 제거 - exception resolver order 기본값 복귀
KiSeungMin Aug 22, 2026
68ae5cf
[Chore] Prometheus 지표 보존 기간 연장 - prod 90일, dev 30일
KiSeungMin Aug 22, 2026
c42e724
[Fix] 문제 메모 255자 초과 시 500 수정 - 컬럼 확장 및 길이 검증 추가
KiSeungMin Sep 2, 2026
c9fb4a0
[Perf] 복습 예정 조회 N+1 제거 - DTO 프로젝션으로 전환
KiSeungMin Sep 2, 2026
347603f
[Fix] 처리된 실패가 Sentry error 로 올라가던 문제 수정
KiSeungMin Sep 2, 2026
90a76bf
[Chore] 5xx 알림 규칙 보정 - API 경로 한정 및 건수 기준 규칙 추가
KiSeungMin Sep 2, 2026
388b9d1
[Chore] 테스트 전체 실행 복구 및 커버리지 측정 도입
KiSeungMin Sep 2, 2026
3ac3234
[Fix] 만료 액세스 토큰이 1005 대신 1007 로 나가던 문제 수정
KiSeungMin Sep 2, 2026
5a173d1
[Perf] 문제 커서 조회의 인메모리 페이징 제거
KiSeungMin Sep 2, 2026
094f61d
[Fix] 알림 설정이 응답에 빠져 껐는데도 켜져 보이던 문제 수정
KiSeungMin Sep 2, 2026
26b9a65
[Fix] 이미지 업로드 한도 초과 시 AI 분석 문구가 뜨던 문제 수정
KiSeungMin Sep 2, 2026
4ffd6c1
[Test] 코드 변경을 못 따라간 테스트 10건 수정
KiSeungMin Sep 2, 2026
13a84ea
[Chore] 테스트 힙 증량 및 JaCoCo 설정 추가
KiSeungMin Sep 2, 2026
bb56a67
[Test] 테스트 간 데이터 오염 제거 - ProblemServiceTest 격리
KiSeungMin Sep 2, 2026
1b1f840
[Docs] Discord webhook consumer 주석 정정
KiSeungMin Sep 2, 2026
1748223
[Fix] 프로덕션 500 오류 5건 수정 및 테스트 실행 정상화
KiSeungMin Sep 2, 2026
3103461
[Test] 테스트 실행 기반을 Testcontainers MySQL로 전환
KiSeungMin Sep 2, 2026
582cdbc
[Test] PR 테스트 게이트 워크플로 추가 및 컬럼 길이 가드 도입
KiSeungMin Sep 2, 2026
74767bf
[Chore] 로컬 인프라를 docker compose 하나로 통합
KiSeungMin Sep 2, 2026
d2049fc
[Test] 도메인 테스트 대폭 확충 및 프로덕션 결함 수정
KiSeungMin Sep 2, 2026
5cfe44b
[Test] 남은 테스트 실패 수정 - problem 도메인 전체 통과
KiSeungMin Sep 2, 2026
46977dc
[Chore] 컬럼 확장 마이그레이션에 프로덕션 실측값 반영
KiSeungMin Sep 2, 2026
dafdc5e
[Chore] V25 주석에 프로덕션 Flyway 상태 반영
KiSeungMin Sep 2, 2026
b87d8a3
[Test] admin·feedback·mission·folder·practicenote 테스트 추가 및 결함 수정
KiSeungMin Sep 2, 2026
cecc11d
[Test] studyroom·learningcalendar·learningreport·quartz 테스트 추가 및 결함 수정
KiSeungMin Sep 2, 2026
d461866
[Test] 아키텍처 규칙·쿼리 수 회귀·뮤테이션 테스트 도입
KiSeungMin Sep 2, 2026
63e8752
[Fix] 메시지 컨슈머·동시성 경합 검증 추가 및 결함 수정
KiSeungMin Sep 2, 2026
b76566d
[Merge] develop 통합 - 프로덕션 수정과 테스트 정비 합류
KiSeungMin Sep 7, 2026
91d09b3
[Chore] CI 테스트 실패 원인이 보이도록 예외 출력 확대
KiSeungMin Sep 7, 2026
ddf9c69
[Fix] CI 테스트가 전멸하던 원인 수정 - application-test.yml 이 커밋되지 않고 있었다
KiSeungMin Sep 7, 2026
d5c1b5b
[Chore] 테스트 예외 출력을 full 로 유지
KiSeungMin Sep 7, 2026
06807da
Merge pull request #231 from AI-SIP/test/overhaul
KiSeungMin Sep 7, 2026
cc51b1d
[Fix] Quartz 기동 실패 수정 - jobStore.class 지정이 DataSource 주입을 덮어썼다
KiSeungMin Sep 7, 2026
29a064f
Merge pull request #240 from AI-SIP/fix/quartz-datasource
KiSeungMin Sep 7, 2026
3afa77b
[Feat] 서비스 공지 도메인과 유저 API 추가
KiSeungMin Sep 7, 2026
40b365e
[Feat] 관리자 공지 관리 페이지 추가
KiSeungMin Sep 7, 2026
67fee45
[Test] 서비스 공지 테스트 추가
KiSeungMin Sep 7, 2026
892c400
Merge pull request #243 from AI-SIP/feat/service-notice
KiSeungMin Sep 7, 2026
ecedbe8
[Feat] 오답노트 복습 기록에 기분 이모지 추가
KiSeungMin Sep 8, 2026
5c7ce3f
[Test] 실행 시각과 토큰 값에 따라 깨지던 테스트 두 건 수정
KiSeungMin Sep 8, 2026
3322318
Merge pull request #245 from AI-SIP/test/fix-flaky-time-dependent
KiSeungMin Sep 8, 2026
1534767
Merge branch 'develop' into feat/solve-mood-emoji
KiSeungMin Sep 8, 2026
b7d29b7
Merge pull request #244 from AI-SIP/feat/solve-mood-emoji
KiSeungMin Sep 8, 2026
bf38bb6
[Fix] 복습 기록 응답의 날짜 형식을 다른 API 와 같은 ISO-8601 로 맞춘다
KiSeungMin Sep 8, 2026
82f1ec0
Merge pull request #246 from AI-SIP/fix/problem-solve-date-format
KiSeungMin Sep 9, 2026
b3e054e
[Feat] 일일·주간 미션 시스템 1차
KiSeungMin Sep 9, 2026
000196f
[Test] 미션 시스템 검증
KiSeungMin Sep 9, 2026
0ddab7a
[Fix] 같은 복습 세트를 반복 완료해도 미션이 오르던 문제
KiSeungMin Sep 9, 2026
6d424a2
[Fix] 미션 진행도 실패가 본 작업을 롤백시키던 문제
KiSeungMin Sep 9, 2026
b78241d
[Fix] 마이그레이션 부분 실패 대비 - DDL 과 시드 분리
KiSeungMin Sep 9, 2026
56e4350
[Fix] 과거 날짜 기분 기록과 받기 쿼리 소유권 조건
KiSeungMin Sep 9, 2026
55558ff
[Feat] 기간이 지난 미수령 보상을 조회에 포함한다
KiSeungMin Sep 9, 2026
2d3d245
[Test] 2차 검증에서 나온 결함 여섯 건을 잠근다
KiSeungMin Sep 9, 2026
ccbbe55
[Refactor] 자동 적립을 미션으로 흡수한다 - XP 는 받을 때만 들어온다
KiSeungMin Sep 10, 2026
4f16812
[Test] 자동 적립 폐지 - 기록은 남고 XP 는 받을 때만 오른다
KiSeungMin Sep 10, 2026
2a2b2eb
[Feat] 미션 보상 획득 기록 조회 API
KiSeungMin Sep 10, 2026
85e3ae3
[Test] 미션 보상 획득 기록 조회
KiSeungMin Sep 10, 2026
9f8e6e4
[Chore] 테스트 커넥션 풀을 명시한다 - 진행도 분리로 요청당 커넥션이 두 개 필요하다
KiSeungMin Sep 10, 2026
4817490
[Fix] 미션 진행도를 호출자 트랜잭션으로 되돌린다 - 커넥션 두 배 문제
KiSeungMin Sep 10, 2026
cf84c9d
[Feat] 보상 스냅샷 - 받은 기록이 나중에 바뀐 보상값을 따라가지 않게
KiSeungMin Sep 10, 2026
006a671
[Test] 보상 스냅샷
KiSeungMin Sep 10, 2026
c560395
[Fix] 지난 미션 정렬이 주간을 항상 앞세우던 문제
KiSeungMin Sep 10, 2026
31045f5
[Refactor] 진행도를 올리지 않던 범용 미션 등록 진입점을 지운다
KiSeungMin Sep 10, 2026
9193042
[Fix] V31·V32 를 재실행에 안전하게
KiSeungMin Sep 10, 2026
06a00a1
[Fix] 기기 시간대가 KST 가 아니면 기분 미션이 안 오르던 문제
KiSeungMin Sep 10, 2026
8589b94
[Chore] 미션 제목과 설명 문구를 다듬는다
KiSeungMin Sep 10, 2026
0f279ab
[Feat] 자동 적립을 설정으로 켜고 끌 수 있게 되살린다
KiSeungMin Sep 10, 2026
503cf29
[Test] 자동 적립 플래그 켜짐/꺼짐
KiSeungMin Sep 10, 2026
591ec5c
[Chore] 테스트 시간대를 운영과 맞추고 pitest 대상에 미션 기간 키를 넣는다
KiSeungMin Sep 10, 2026
cf3e486
[Test] ZoneId.systemDefault() 호출을 아키텍처 규칙으로 막는다
KiSeungMin Sep 10, 2026
4c9913b
[Test] 가드를 빼면 실패하도록 안 덮인 경계를 채운다
KiSeungMin Sep 10, 2026
287fe58
[Test] 약한 단언을 값으로 고정하고 임계치 경계를 채운다
KiSeungMin Sep 10, 2026
fd20bbc
Merge pull request #248 from AI-SIP/feat/mission-engine
KiSeungMin Sep 10, 2026
33ca2c4
[Feat] 꾸미기 시스템 스키마와 아이템 시드 추가
KiSeungMin Sep 11, 2026
01a3bce
[Feat] 꾸미기 엔티티와 리포지토리 추가
KiSeungMin Sep 11, 2026
a5592b9
[Feat] 꾸미기 조회·장착 서비스 추가
KiSeungMin Sep 11, 2026
ab2cd63
[Feat] 꾸미기 API 3종 추가
KiSeungMin Sep 11, 2026
b45281c
[Feat] 미션 보상 수령 응답에 레벨업 해금 아이템 싣기
KiSeungMin Sep 11, 2026
0cd3cc4
[Test] 꾸미기 서비스와 API 계약 테스트 추가
KiSeungMin Sep 11, 2026
3562fd8
[Test] 꾸미기 동시성·마이그레이션·미션 연동 테스트 추가
KiSeungMin Sep 11, 2026
d364744
[Feat] 총 학습 레벨 상한을 15 에서 20 으로
KiSeungMin Sep 11, 2026
05fbcb4
[Feat] 꾸미기 카탈로그 마이그레이션 - 능력치 컬럼 추가와 시드 55개 교체
KiSeungMin Sep 11, 2026
c84cfa5
[Feat] 꾸미기 보유 판정과 응답을 능력치별 기준으로
KiSeungMin Sep 11, 2026
9fe6520
[Feat] 꾸미기 시드에 프로필 프레임 8종 추가
KiSeungMin Sep 12, 2026
519cc2c
[Feat] FRAME 자리 추가 - 개구리에 겹치지 않는 자리를 데이터로 구분
KiSeungMin Sep 12, 2026
24f6866
[Fix] 기본 프리셋에서 프레임을 뺀다 - 스터디룸에서 나만 테두리가 생긴다
KiSeungMin Sep 12, 2026
2a945ad
[Chore] BACK 자리 이름을 등짐에서 배낭으로
KiSeungMin Sep 12, 2026
ec6ac3f
[Fix] 슬롯 컬럼을 varchar(32) 로 못 박는다
KiSeungMin Sep 12, 2026
08b92d1
[Feat] 가방 자리 통합 마이그레이션 - layer_order 컬럼과 BACK 정리
KiSeungMin Sep 12, 2026
f5f4380
[Feat] 가방 자리를 하나로 합친다 - 그리는 층은 아이템이 덮어쓴다
KiSeungMin Sep 12, 2026
0b340cb
Merge pull request #250 from AI-SIP/feat/cosmetic-system
KiSeungMin Sep 12, 2026
b8f8013
[Fix] 능력치 레벨 상한을 15 에서 20 으로
KiSeungMin Sep 12, 2026
8c132f0
[Fix] 상한 레벨의 잔여 포인트를 게이지 분모 안으로 누른다
KiSeungMin Sep 12, 2026
647ad98
[Test] 능력치 상한과 총 학습 상한의 대응을 못 박는다
KiSeungMin Sep 12, 2026
dfa7098
Merge pull request #252 from AI-SIP/fix/ability-level-cap
KiSeungMin Sep 13, 2026
7f85373
[Feat] 차림 전체를 한 번에 저장하는 PUT /api/cosmetics/equip-all
KiSeungMin Sep 13, 2026
225ecdf
[Test] 꾸미기 응답을 골든 파일로 굳혀 프론트와 같은 JSON 을 보게 한다
KiSeungMin Sep 13, 2026
5476741
Merge pull request #254 from AI-SIP/feat/cosmetic-equip-all
KiSeungMin Sep 13, 2026
bf1f02b
[Feat] 훈장 12종 판정과 조회 API
KiSeungMin Sep 13, 2026
e8d6b58
[Fix] 정리의 신에서 루트 폴더를 빼고 센다
KiSeungMin Sep 13, 2026
81a96a5
Merge pull request #256 from AI-SIP/feat/achievement-medals
KiSeungMin Sep 13, 2026
0d8b7ee
[Fix] 자동 적립을 앱 버전으로 갈라 XP 이중 지급을 끊는다
KiSeungMin Sep 14, 2026
5b147fb
Merge pull request #258 from AI-SIP/fix/dual-xp-accrual
KiSeungMin Sep 14, 2026
36f8b72
[Chore] 실제 도메인으로 조회 API 를 불러 보는 스모크 테스트를 추가한다
KiSeungMin Sep 15, 2026
3427a0b
[Chore] 스모크 전용 게스트 계정으로 조회와 쓰기 흐름까지 확인한다
KiSeungMin Sep 15, 2026
489b928
[Chore] 배포가 끝난 뒤와 매시간 스모크 테스트를 돌리고 Discord 로 알린다
KiSeungMin Sep 15, 2026
ccd0225
Merge pull request #263 from AI-SIP/chore/smoke-test
KiSeungMin Sep 15, 2026
a9ce8c4
[Chore] agent-config-lint 에서 테스트용 application-test.yml 만 예외로 둔다
KiSeungMin Sep 15, 2026
653cf1b
Merge pull request #264 from AI-SIP/chore/lint-allow-test-config
KiSeungMin Sep 15, 2026
8c06c40
[Fix] 복습 리마인더에 발송 시간대 가드와 기한 만료를 넣는다
KiSeungMin Sep 14, 2026
443a02a
[Fix] 이모지 화이트리스트에 앱이 새로 연 키 네 개를 추가한다
KiSeungMin Sep 17, 2026
8452a91
[Fix] 같은 날 두 번째 복습 이미지 등록을 다시 허용한다
KiSeungMin Sep 14, 2026
d9b9d32
[Fix] Quartz JobFactory 가 ApplicationContext 를 부모에 넘기게 한다
KiSeungMin Sep 17, 2026
c5f437c
[Fix] 스터디룸 주간 리포트 잡을 스케줄러에 등록한다
KiSeungMin Sep 17, 2026
6b9dd29
[Fix] 방장이 회원 탈퇴하면 남은 멤버가 스터디룸을 관리할 수 없던 것
KiSeungMin Sep 17, 2026
3470128
[Fix] 리프레시 토큰을 찾지 못하면 404 대신 401 로 응답한다
KiSeungMin Sep 17, 2026
8c27b17
[Fix] RabbitMQ 로그와 DLQ 알림에 실제 처리 시도 횟수를 남긴다
KiSeungMin Sep 17, 2026
e1936a2
Merge pull request #274 from AI-SIP/fix/study-room-host-withdrawal
KiSeungMin Sep 17, 2026
4a8462f
Merge pull request #276 from AI-SIP/fix/quartz-job-registration
KiSeungMin Sep 17, 2026
7c0b360
Merge pull request #275 from AI-SIP/fix/custom-emoji-allowlist
KiSeungMin Sep 17, 2026
8b83090
[Fix] FCM 큐 적재 실패가 호출자 트랜잭션을 롤백시키지 않게 한다
KiSeungMin Sep 17, 2026
3faee3b
[Fix] 자동 적립이 도는 요청에서는 미션 진행도를 올리지 않는다
KiSeungMin Sep 17, 2026
e6a2431
Merge pull request #277 from AI-SIP/fix/review-reminder-send-window
KiSeungMin Sep 17, 2026
849dfa0
Merge pull request #279 from AI-SIP/fix/allow-multiple-solve-images
KiSeungMin Sep 17, 2026
58f4b8c
Merge pull request #278 from AI-SIP/fix/refresh-token-not-found-401
KiSeungMin Sep 17, 2026
734db5f
Merge pull request #281 from AI-SIP/fix/fcm-enqueue-failure-rollback
KiSeungMin Sep 17, 2026
d20f232
Merge pull request #280 from AI-SIP/fix/mission-progress-version-gate
KiSeungMin Sep 17, 2026
61c52f9
[Fix] 같은 기기에서 계정이 바뀌면 이전 사용자의 FCM 토큰 행을 지운다
KiSeungMin Sep 17, 2026
ab8d9da
[Fix] ERROR 로 멈춘 Quartz 트리거를 5분마다 점검해 되돌린다
KiSeungMin Sep 17, 2026
cdab1da
[Fix] 폴더 삭제와 문제 등록이 겹쳐도 삭제된 폴더를 가리키는 문제가 남지 않게 한다
KiSeungMin Sep 17, 2026
09b0560
[Fix] 던지는 곳이 없는 StudyRoomErrorCase 세 개를 지운다
KiSeungMin Sep 17, 2026
329e673
Merge pull request #283 from AI-SIP/fix/rabbitmq-retry-count
KiSeungMin Sep 17, 2026
6c5e121
Merge pull request #282 from AI-SIP/fix/fcm-token-owner-switch
KiSeungMin Sep 17, 2026
f4dcff1
Merge pull request #284 from AI-SIP/fix/folder-delete-problem-race
KiSeungMin Sep 17, 2026
4bbe0ef
Merge pull request #285 from AI-SIP/fix/reminder-trigger-error-recovery
KiSeungMin Sep 17, 2026
ea7c671
Merge pull request #286 from AI-SIP/fix/remove-unreachable-studyroom-…
KiSeungMin Sep 17, 2026
d59da22
[Fix] FCM 토큰 등록이 동시에 들어와도 500 이 되지 않게 한다
KiSeungMin Sep 18, 2026
4a600d0
[Fix] 복습 리마인더 하루 1건 판정에 선점(SENDING) 행도 포함시킨다
KiSeungMin Sep 18, 2026
6206d73
Merge pull request #292 from AI-SIP/fix/reminder-duplicate-send-guard
KiSeungMin Sep 18, 2026
5b78aa0
[Fix] 전체 폴더 삭제와 폴더 생성/수정에도 폴더 잠금을 넣는다
KiSeungMin Sep 18, 2026
96d6d8b
Merge pull request #293 from AI-SIP/fix/fcm-token-duplicate-registration
KiSeungMin Sep 18, 2026
918096a
Merge pull request #294 from AI-SIP/fix/folder-delete-race-remaining-…
KiSeungMin Sep 18, 2026
5152d82
[Fix] 챌린지 알림을 아침 09시나 저녁 18시에만 보낸다
KiSeungMin Sep 18, 2026
5de54ba
Merge pull request #295 from AI-SIP/fix/challenge-notification-time
KiSeungMin Sep 18, 2026
54b4a8e
[Fix] Quartz 스키마 초기화가 기존 QRTZ_ 테이블을 지우지 않게 한다
KiSeungMin Sep 18, 2026
f4b9891
Merge pull request #296 from AI-SIP/fix/quartz-schema-persist
KiSeungMin Sep 18, 2026
5426515
[Fix] FCM 토큰 등록이 교착으로 깨져도 다시 시도한다
KiSeungMin Sep 18, 2026
67642a0
Merge pull request #297 from AI-SIP/fix/fcm-token-register-deadlock-r…
KiSeungMin Sep 18, 2026
5cb05d7
[Fix] 인증 없는 로그아웃이 200 으로 나가지 않게 한다
KiSeungMin Sep 19, 2026
88afa3a
[Fix] 복습노트 알림에 type 을 넣고 알림 type 표기를 소문자로 통일한다
KiSeungMin Sep 19, 2026
8cb136b
Merge pull request #311 from AI-SIP/fix/auth-endpoint-protection
KiSeungMin Sep 19, 2026
c912d04
Merge pull request #312 from AI-SIP/fix/notification-type-key
KiSeungMin Sep 19, 2026
56e7d80
[Fix] 탈퇴할 때 그 사용자의 리프레시 토큰 행을 지운다
KiSeungMin Sep 19, 2026
6927b71
Merge pull request #313 from AI-SIP/fix/withdrawal-session-cleanup
KiSeungMin Sep 19, 2026
08e7ac6
[Fix] 탈퇴한 계정의 액세스 토큰을 더 이상 받지 않는다
KiSeungMin Sep 19, 2026
32d27be
[Fix] 요일을 고르지 않은 주간 복습 알림을 400 으로 거절한다
KiSeungMin Sep 19, 2026
7c344e5
[Fix] 챌린지를 만든 본인도 지울 수 있게 한다
KiSeungMin Sep 19, 2026
8906465
Merge pull request #314 from AI-SIP/fix/deleted-user-access-token
KiSeungMin Sep 19, 2026
b829031
Merge pull request #315 from AI-SIP/fix/practice-weekday-validation
KiSeungMin Sep 19, 2026
d23d0bf
Merge pull request #316 from AI-SIP/fix/challenge-delete-permission
KiSeungMin Sep 19, 2026
2f5f7b4
[Fix] 신버전이 남긴 mission_log 가 구버전 적립을 막지 않게 한다
KiSeungMin Sep 19, 2026
cf76def
[Fix] 요일 없는 주간 복습 알림 검증을 앱 버전으로 가른다
KiSeungMin Sep 19, 2026
94ece8f
Merge pull request #320 from AI-SIP/fix/mission-log-cross-version
KiSeungMin Sep 19, 2026
1aa1b8f
Merge pull request #321 from AI-SIP/fix/weekday-validation-version-gate
KiSeungMin Sep 19, 2026
277daef
[Fix] 폴더 삭제 잠금이 다른 폴더와 다른 사용자에게 번지지 않게 한다
KiSeungMin Sep 19, 2026
3541ca8
Merge pull request #322 from AI-SIP/fix/folder-lock-scope
KiSeungMin Sep 19, 2026
1ccf15b
[Chore] 스모크 테스트가 기존 JWT 서명 키 시크릿을 그대로 쓰게 한다
KiSeungMin Sep 19, 2026
f7042c9
Merge pull request #330 from AI-SIP/chore/smoke-reuse-jwt-secret
KiSeungMin Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 2 additions & 1 deletion .claude/commands/feat.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ argument-hint: "<작업 설명 또는 계획서 경로>"
- 가드레일 준수: FCM 경로·프로덕션 DB 파괴 작업 전 경고.
- 사용자 격리(userId 소유권 검증) 누락 없이 구현.
- 근거 없는 코드 추가 금지. 각 변경은 계획서 단계에 대응.
- 각 단위 구현 이후, 5번 섹션의 커밋 진행

4. **컴파일 확인** — 구현 완료 후 `./gradlew compileJava` 실행. 오류 있으면 수정.

5. **커밋 제안** — 논리 단위별 커밋 메시지 초안을 제시하고 사용자 확인 후 커밋.
5. **커밋 진행** — 논리 단위별로 알맞게 분리해 자체적으로 커밋
형식: `[Feat] 한 줄 요약\n- 상세`
(테스트는 /test로 분리)

Expand Down
7 changes: 6 additions & 1 deletion .codex/scripts/agent-config-lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ if grep -q '^/AGENTS\.md$' .gitignore; then
error "AGENTS.md is ignored; shared agent guidance should be trackable"
fi

# src/test/resources/application-test.yml 은 예외다. CI 테스트가 이 파일로 Testcontainers 환경을 띄워서
# 커밋해야 하고(ddf9c69), 값은 전부 테스트 전용 더미라 운영 설정과 겹치지 않는다.
# 아래 secret_hits 검사는 이 파일에도 그대로 적용된다.
tracked_secret_files="$(
git ls-files | grep -E '(^|/)(\.env(\..*)?|FirebaseAdminKey\.json|application(-(dev|prod|local|test))?\.ya?ml|application(-(dev|prod|local|test))?\.properties)$' || true
git ls-files \
| grep -E '(^|/)(\.env(\..*)?|FirebaseAdminKey\.json|application(-(dev|prod|local|test))?\.ya?ml|application(-(dev|prod|local|test))?\.properties)$' \
| grep -v -x 'src/test/resources/application-test\.yml' || true
)"

if [ -n "$tracked_secret_files" ]; then
Expand Down
230 changes: 230 additions & 0 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
name: Smoke Test

# 배포된 서버가 사용자를 받을 수 있는 상태인지 실제 도메인으로 확인한다. 자세한 내용은 scripts/smoke/README.md 에 있다.
#
# 언제 도는가
# - 배포 워크플로가 끝난 뒤: 배포가 성공했으면 full(조회 + 스모크 계정 안의 쓰기 흐름), 실패했으면 read.
# - 매시 17분: prod 에 read. 맥미니나 네트워크가 죽은 것을 배포와 상관없이 잡는다.
# - 수동 실행: 대상과 단계를 고른다. bootstrap 은 스모크 계정을 처음 한 번 만들 때만 쓴다.
#
# 운영 서버에 영향을 주지 않으려고 지키는 것
# - 배포 워크플로(ci-prod / deploy-prod / ci-dev / deploy-dev)는 수정하지 않았다. 끝난 것을 workflow_run 으로 받아서
# 따로 돌기 때문에, 스모크 테스트가 틀려도 배포와 nginx 전환에는 아무 영향이 없다.
# - 맥미니 러너(self-hosted)가 아니라 GitHub 호스티드 러너에서 돈다.
# - 스크립트 자체 테스트가 먼저 통과해야 실제 서버에 요청을 보낸다.
# - 보낼 수 있는 요청과 요청 수 상한은 스크립트에 고정되어 있다.
#
# 필요한 Secrets (없으면 할 수 있는 단계까지만 한다)
# JWT_ACCESS_TOKEN_SECRET, SMOKE_PROD_USER_ID, SMOKE_DEV_USER_ID, DISCORD_WEBHOOK_URL
#
# JWT_ACCESS_TOKEN_SECRET 은 배포 워크플로가 서버에 넘기는 그 키다(ci-dev.yml, ci-prod.yml). 스모크는 같은 키로
# 토큰을 직접 만들어 쓰기 때문에 값이 같아야 하고, 따로 복사해 두면 키를 바꿀 때 한쪽만 남아 알림이 계속 울린다.
#
# workflow_run 과 schedule 은 이 파일이 기본 브랜치(main)에 있어야 동작한다.

on:
workflow_dispatch:
inputs:
target:
description: "확인할 서버"
type: choice
options: [prod, dev]
default: prod
level:
description: "reach: 닿는지만 / read: 조회 / full: 조회 + 쓰기 흐름 / bootstrap: 스모크 계정 만들기(처음 한 번)"
type: choice
options: [read, full, reach, bootstrap]
default: read
workflow_run:
workflows:
- "CI Build & Deploy Prod"
- "Deploy to Production Server (Manual Fallback)"
- "CI Build & Deploy Dev"
- "Deploy to Development Server (Manual Fallback)"
types: [completed]
schedule:
- cron: "17 * * * *"

permissions:
contents: read
actions: read

# 쓰기가 있는 실행(배포 성공 뒤 full, 수동 full, bootstrap)만 대상별로 한 줄로 세운다. 같은 스모크 계정에 쓰기가 겹치지 않게 한다.
# 조회만 하는 실행은 실행마다 그룹을 따로 둔다. 같은 그룹에 대기 중인 실행이 새 실행에 밀려 취소되는 일을 막는다.
concurrency:
group: >-
${{
((github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
|| (github.event_name == 'workflow_dispatch' && (inputs.level == 'full' || inputs.level == 'bootstrap')))
&& format('smoke-write-{0}', github.event_name == 'workflow_dispatch' && inputs.target || (contains(github.event.workflow_run.name, 'Dev') && 'dev' || 'prod'))
|| format('smoke-read-{0}', github.run_id)
}}
cancel-in-progress: false

jobs:
smoke:
name: 스모크 테스트
# 배포가 취소되거나 건너뛰어졌으면 확인할 것이 없다.
if: github.event_name != 'workflow_run' || contains(fromJSON('["success","failure"]'), github.event.workflow_run.conclusion)
runs-on: ubuntu-latest
# 스크립트 자체 테스트 약 30초 + 스모크 실행 상한 200초 + 준비 시간
timeout-minutes: 8

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: 대상과 단계 정하기
id: plan
env:
EVENT: ${{ github.event_name }}
INPUT_TARGET: ${{ inputs.target }}
INPUT_LEVEL: ${{ inputs.level }}
REF: ${{ github.ref }}
RUN_NAME: ${{ github.event.workflow_run.name }}
RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
run: |
case "$EVENT" in
workflow_dispatch)
target="$INPUT_TARGET"; level="$INPUT_LEVEL" ;;
workflow_run)
# "CI Build & Deploy Dev", "Deploy to Development Server ..." 는 둘 다 Dev 를 포함한다.
case "$RUN_NAME" in *Dev*) target="dev" ;; *) target="prod" ;; esac
if [ "$RUN_CONCLUSION" = "success" ]; then level="full"; else level="read"; fi ;;
schedule)
target="prod"; level="read" ;;
*)
echo "알 수 없는 이벤트: $EVENT"; exit 2 ;;
esac
# 수동 실행은 아무 브랜치에서나 할 수 있어서, prod 는 main 의 스크립트로만 돌린다.
if [ "$EVENT" = "workflow_dispatch" ] && [ "$target" = "prod" ] && [ "$REF" != "refs/heads/main" ]; then
echo "prod 대상 수동 실행은 main 브랜치에서만 할 수 있습니다 (현재 $REF)"; exit 2
fi
case "$target" in
prod) url="https://ono-prod.seungminki.shop" ;;
dev) url="https://ono-dev.seungminki.shop" ;;
*) echo "알 수 없는 대상: $target"; exit 2 ;;
esac
{
echo "target=$target"
echo "level=$level"
echo "url=$url"
} >> "$GITHUB_OUTPUT"
echo "대상 $target ($url), 단계 $level"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: 스크립트 자체 테스트
run: python3 -m unittest discover -s scripts/smoke -p 'test_*.py'

# 대상 서버의 시크릿만 그 스텝에 넘긴다. 한 스텝에 dev 와 prod 시크릿을 같이 두지 않는다.
- name: 스모크 테스트 (prod)
id: smoke_prod
if: steps.plan.outputs.target == 'prod'
env:
SMOKE_BASE_URL: ${{ steps.plan.outputs.url }}
SMOKE_LEVEL: ${{ steps.plan.outputs.level }}
SMOKE_ACCESS_TOKEN_SECRET: ${{ secrets.JWT_ACCESS_TOKEN_SECRET }}
SMOKE_USER_ID: ${{ secrets.SMOKE_PROD_USER_ID }}
run: |
if [ "$SMOKE_LEVEL" = "bootstrap" ]; then
unset SMOKE_ACCESS_TOKEN_SECRET
python3 scripts/smoke/smoke_test.py bootstrap
else
python3 scripts/smoke/smoke_test.py
fi

- name: 스모크 테스트 (dev)
id: smoke_dev
if: steps.plan.outputs.target == 'dev'
env:
SMOKE_BASE_URL: ${{ steps.plan.outputs.url }}
SMOKE_LEVEL: ${{ steps.plan.outputs.level }}
SMOKE_ACCESS_TOKEN_SECRET: ${{ secrets.JWT_ACCESS_TOKEN_SECRET }}
SMOKE_USER_ID: ${{ secrets.SMOKE_DEV_USER_ID }}
run: |
if [ "$SMOKE_LEVEL" = "bootstrap" ]; then
unset SMOKE_ACCESS_TOKEN_SECRET
python3 scripts/smoke/smoke_test.py bootstrap
else
python3 scripts/smoke/smoke_test.py
fi

# 알림 규칙
# - 배포 뒤 확인: 통과와 실패 모두 알린다.
# - 매시 확인: 정상에서 실패로 바뀔 때와 복구될 때 알린다. 실패가 이어지면 6시간마다 한 번 다시 알린다.
# 첫 알림 전송이 실패해도 늦어도 6시간 뒤에는 알림이 가게 하려는 것이다.
# - 수동 실행과 bootstrap: 알리지 않는다. 실행한 사람이 화면에서 본다.
- name: Discord 알림
if: always() && steps.plan.outputs.level != 'bootstrap' && github.event_name != 'workflow_dispatch'
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
EVENT: ${{ github.event_name }}
TARGET: ${{ steps.plan.outputs.target }}
LEVEL: ${{ steps.plan.outputs.level }}
# 대상이 아닌 스텝의 outcome 은 빈 값이 아니라 skipped 라서, 대상으로 골라야 한다.
OUTCOME: ${{ steps.plan.outputs.target == 'prod' && steps.smoke_prod.outcome || steps.smoke_dev.outcome }}
FIRST_FAILURE: ${{ steps.plan.outputs.target == 'prod' && steps.smoke_prod.outputs.first_failure || steps.smoke_dev.outputs.first_failure }}
FAILED_COUNT: ${{ steps.plan.outputs.target == 'prod' && steps.smoke_prod.outputs.failed || steps.smoke_dev.outputs.failed }}
DEPLOY_NAME: ${{ github.event.workflow_run.name }}
DEPLOY_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
REPO: ${{ github.repository }}
run: |
if [ -z "$WEBHOOK" ]; then
echo "DISCORD_WEBHOOK_URL 이 없어 알림을 건너뜁니다"; exit 0
fi
if [ "$OUTCOME" = "success" ]; then passed=true; else passed=false; fi

streak=0
if [ "$EVENT" = "schedule" ]; then
# 직전 매시 실행들이 연속으로 몇 번 성공하지 못했는지 센다. cancelled, timed_out 도 실패로 본다.
# 조회에 실패하면 알림을 보내는 쪽으로 기운다(streak=0).
conclusions=$(gh api "repos/$REPO/actions/workflows/smoke-test.yml/runs?event=schedule&status=completed&per_page=12" \
--jq '.workflow_runs[].conclusion' 2>/dev/null || true)
previous=""
for c in $conclusions; do
[ -z "$previous" ] && previous="$c"
if [ "$c" = "success" ]; then break; fi
streak=$((streak + 1))
done
if $passed && [ "$streak" -eq 0 ]; then echo "정상 유지, 알림 없음"; exit 0; fi
# 직전이 failure 일 때만 "이미 알렸다"고 본다. cancelled 나 timed_out 은 알림 스텝이 못 돌았을 수 있다.
if ! $passed && [ "$previous" = "failure" ] && [ $((streak % 6)) -ne 0 ]; then
echo "장애 지속 (직전 연속 실패 $streak 회), 6회마다 다시 알립니다"; exit 0
fi
fi

export PASSED=$passed STREAK=$streak
python3 - <<'PY' > payload.json
import json, os
env = os.environ
passed = env["PASSED"] == "true"
event, target, level = env["EVENT"], env["TARGET"], env["LEVEL"]
if event == "schedule":
streak = int(env.get("STREAK") or 0)
if passed:
title = f"✅ 스모크 테스트 복구 ({target})"
elif streak:
title = f"🚨 스모크 테스트 실패 {streak + 1}시간째 ({target})"
else:
title = f"🚨 스모크 테스트 실패 ({target})"
context = "매시 확인"
else:
title = f"{'✅' if passed else '🚨'} 배포 후 스모크 테스트 {'통과' if passed else '실패'} ({target})"
context = f"{env.get('DEPLOY_NAME', '')} ({env.get('DEPLOY_CONCLUSION', '')}) 뒤 확인"
lines = [f"{context}, 단계 `{level}`"]
if not passed:
lines.append(f"실패 {env.get('FAILED_COUNT') or '?'}건. 첫 실패: {env.get('FIRST_FAILURE') or '스크립트가 끝까지 돌지 못했습니다'}")
lines.append(env["RUN_URL"])
# 실패 문구에 서버 응답 일부가 들어가므로 @everyone 같은 멘션이 발동하지 않게 막는다.
print(json.dumps({"allowed_mentions": {"parse": []},
"embeds": [{"title": title, "description": "\n".join(lines),
"color": 3066993 if passed else 15158332}]}))
PY
curl -fsS -X POST "$WEBHOOK" -H "Content-Type: application/json" --data @payload.json
Loading
Loading