Skip to content

feat: 휴지통 목록 API (#54) - #55

Merged
oTAETAEo merged 5 commits into
developfrom
feat/54-trash-list
Nov 2, 2026
Merged

oTAETAEo merged 5 commits into
developfrom
feat/54-trash-list

Conversation

@oTAETAEo

@oTAETAEo oTAETAEo commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

관련 이슈

작업 내용

명세 15번 GET /api/v1/trash/templates를 구현했습니다. 16·17·18번이 이어서 쓸 TrashController, TrashSuccessCode, TrashErrorCode도 여기서 만듭니다.

커밋별로 보기

  1. 조회: TemplateRepository 메서드 3개, TrashQueryService, TrashCursor, TrashErrorCode, 결과 객체
  2. API: TrashController, TrashSuccessCode, 응답 객체
  3. 보관 기간 30일을 TrashRetention으로 분리
  4. TrashCursor를 service 패키지로 이동 (이 API만 쓰는 페이지 처리라 패키지 안으로 감춤)
  5. 목록을 TrashTemplateRow로 조회해 본문을 읽지 않도록 변경

동작 규칙

경우 결과
정렬 deleted_at 내림차순, 동률은 id 내림차순
size 생략 또는 비어 있음 20
cursor 생략 또는 비어 있음 첫 페이지
size 0 이하 또는 51 이상, 해석할 수 없는 커서 TRASH400_1
숫자가 아닌 size COMMON400_1
마지막 페이지 hasNext: false, nextCursor: null
totalCount 페이지와 별도로 센 본인 휴지통 전체 개수
purgeAt deleted_at + 30일. 지난 항목도 배치가 지우기 전까지 그대로 보여 줌

커서는 마지막 항목의 (deletedAt, id)를 이어 붙여 Base64 URL로 인코딩합니다. 응답 시각은 초까지만 나가지만 커서에는 마이크로초까지 넣어서, 같은 초 안의 항목이 페이지 경계에 걸려도 건너뛰지 않습니다.

명세와 달라진 점

  • int 범위를 넘는 size(예: 99999999999)는 명세의 TRASH400_1이 아니라 COMMON400_1입니다. 범위 검사 전에 숫자 변환에서 실패하기 때문입니다. 이 동작은 테스트로 고정했습니다

테스트

  • TrashControllerTest (테스트컨테이너 MySQL)
    • 정렬, 동률의 id 내림차순, 필드 순서, +09:00, no-store, purgeAt, totalCount
    • 빈 휴지통, 활성 템플릿과 다른 회원 휴지통 제외, 예정 시각이 지난 항목 노출
    • size 기본값, 경계 1·50, 0·51·-1 TRASH400_1, 숫자가 아닌 값 COMMON400_1, 빈 파라미터
    • 끝까지 넘겨도 누락·중복 없음, 마이크로초만 다른 항목이 페이지 경계에 걸려도 누락 없음
    • 페이지 사이에 복구·영구 삭제·새 삭제가 일어나도 누락·중복 없음
    • 잘못된 커서 TRASH400_1, 0000년·9999년 커서도 500 없음, 토큰 없음 COMMON401_1
    • 본문을 복호화할 수 없는 행이 있어도 목록은 200
  • TrashTemplateRepositoryTest: 정렬, 커서 조건, 마이크로초 비교, 본인 휴지통만 읽고 세기
  • TrashQueryServiceTest (모키토): 첫·다음 페이지 분기, size + 1 조회, 커서 생성, 잘못된 커서면 DB를 읽지 않음
  • TrashCursorTest: 왕복 변환, URL 안전 문자, 서버가 만든 형식이 아닌 값 거절
  • TrashRetentionTest: 삭제 시각의 30일 뒤
  • TrashErrorCodeTest: 결과 코드와 문구
  • 전체 696개 통과

확인 체크리스트

  • ./gradlew build 통과
  • 테스트 포함
  • 변경 400줄 안팎 → 1,132줄 (본 코드 332줄, 테스트 800줄). 테스트가 대부분이고 기능을 나눌 수 없어 그대로 뒀습니다
  • 병합된 마이그레이션 수정 안 함
  • 비밀 값·개인정보 없음
  • global 공통 코드 변경 없음

리뷰어에게

  1. JPQL을 썼습니다: 아직 QueryDSL을 쓰는 조회가 없고, 쓰려면 커스텀 레포지토리가 필요합니다. 첫 페이지와 다음 페이지를 쿼리 2개로 나누면 메서드 추가로 끝납니다
  2. 인덱스는 추가하지 않았습니다: MySQL 8.4 EXPLAIN에서 세 쿼리 모두 idx_templates_user_deleted를 탔습니다. 목록 두 쿼리는 파일 정렬 없는 역방향 스캔이고, 개수는 커버링 인덱스입니다
  3. Row를 썼습니다 (CLAUDE.md 규칙의 예외): Row는 성능 문제가 확인됐을 때만 쓰는 규칙이지만, 이번에는 장애 격리 때문입니다. 엔티티로 읽으면 내보내지도 않는 본문을 복호화해서, 복호화에 실패한 행 하나가 목록 전체를 500으로 만듭니다. 테스트로 재현한 뒤 고쳤습니다
  4. 30일 규칙 위치: support/TrashRetention에 뒀습니다. feat: 내 템플릿 상세 조회 API (#49) #50·#53이 수정 중인 Template을 피했고, 상태 없는 순수 규칙이라 정리 배치도 같은 곳을 쓰면 됩니다
  5. 복구는 흉내 낸 테스트입니다: 16번이 아직 없어 페이지 사이 복구는 네이티브 SQL로 deleted_at을 비웠습니다. 16번이 나오면 실제 복구로 바꿀 수 있습니다

🤖 Generated with Claude Code

@oTAETAEo oTAETAEo added feature 기능 개발 template 내 템플릿·휴지통 labels Oct 31, 2026
@oTAETAEo oTAETAEo self-assigned this Oct 31, 2026
@oTAETAEo
oTAETAEo marked this pull request as ready for review November 1, 2026 10:51
chjpooh2002
chjpooh2002 previously approved these changes Nov 2, 2026

@chjpooh2002 chjpooh2002 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~ 리뷰 내용 한 번 확인 부탁드려요!

private List<Template> findPageWithOneMore(TrashListQuery query) {
Limit limit = Limit.of(query.size() + 1);
if (query.cursor() == null || query.cursor().isEmpty()) {
return templateRepository.findTrashFirstPage(query.userId(), limit);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

휴지통 목록은 본문을 내보내지 않는데 엔티티를 통째로 읽어서, 최대 51개 행의 본문을 매번 복호화하고 있습니다. 비용도 있지만, 복호화가 실패하는 행이 하나라도 생기면 본문을 보여주지도 않는 휴지통 목록 전체가 500이 될 거 같아요! 필요한 컬럼만 고르는 조회(repository/dto의 행 객체)로 바꾸는 걸 고려하면 어떨까 싶습니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 맞네여 휴지통 목록은 본문을 안보여주니까, 말씀 주신 부분 맞습니다 감사합니당 😄

제안 해 주신 repository/dto로 가면 좋을것 같은 생각이여서 해당 부분 반영 하겠습니다 !

* 같은 초 안에서도 순서가 갈리므로 시각은 DATETIME(6)과 같은 마이크로초까지 넣는다.
* 해석할 때 자릿수를 고정해 DB가 받지 못하는 연도(+12026)가 쿼리까지 가지 않게 한다.
*/
public record TrashCursor(LocalDateTime deletedAt, Long id) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support는 "다른 도메인이 써도 되는 순수 규칙" 자리인데, 커서는 이 API의 페이지 처리 방식이라 다른 도메인이 쓸 일이 없어 보입니다. service 쪽으로 옮기는 게 컨벤션 의도에 맞을 것 같아요!
TrashRetention은 정리 배치와 같이 쓰니 support에 두는 게 맞다고 봅니다!

@oTAETAEo oTAETAEo Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다! 반영 하겠습니당 👍

template.getTitle().getValue(),
template.getShortcut().getValue(),
template.getDeletedAt(),
TrashRetention.purgeAt(template.getDeletedAt())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예정 시각이 지났는데 배치가 아직 안 돈 항목이 "과거의 삭제 예정 시각"을 달고 그대로 보이는데, 화면에서 이걸 어떻게 표시할지 기획/프론트랑 한 번 얘기해봐야 될 거 같은데 어떻게 생각하시나요?!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니당 피그마 보니까 삭제 시점을 노출하는 것으로 되어있기는 한데, 요게 어떤식으로 노출이 되는지 잘 모르기도 하구
몇 시간에서 최대 하루 정도 더 보일 뿐이여서 해당 부분 얘기 나누면 좋을 부분같아여

서버에서 안보이게 막기에는 조큼 귀찮아질것 같긴 합니다아 .. ㅋㅎㅋㅎ

@chjpooh2002 chjpooh2002 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~~

@oTAETAEo
oTAETAEo merged commit e208e2f into develop Nov 2, 2026
1 check passed
@oTAETAEo
oTAETAEo deleted the feat/54-trash-list branch November 2, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 기능 개발 template 내 템플릿·휴지통

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 휴지통 목록 API

2 participants