Repository navigation
feat: 휴지통 템플릿 복구 API (#67) #69
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
Changes from all commits
6734ffb
6bcda8b
bc55b38
17906d6
c6f842e
5be4385
f27b3b0
e74cbd5
aae730a
0fd93c8
3673386
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package com.sniptext.domain.template.controller.dto; | ||
|
|
||
| import com.sniptext.domain.template.service.dto.result.TemplateShortcutAssignResult; | ||
|
|
||
| /** 휴지통 복구 결과 (명세 16번). shortcutChanged면 화면이 자동 번호 안내 토스트를 띄운다 */ | ||
| public record TrashRestoreResponse( | ||
| Long templateId, | ||
| String title, | ||
| String shortcut, | ||
| String originalShortcut, | ||
| boolean shortcutChanged | ||
| ) { | ||
|
|
||
| public static TrashRestoreResponse from(TemplateShortcutAssignResult result) { | ||
| return new TrashRestoreResponse( | ||
| result.templateId(), | ||
| result.title(), | ||
| result.shortcut(), | ||
| result.originalShortcut(), | ||
| result.shortcutChanged() | ||
| ); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package com.sniptext.domain.template.service; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.sniptext.domain.template.entity.Shortcut; | ||
| import com.sniptext.domain.template.entity.Template; | ||
| import com.sniptext.domain.template.exception.TrashErrorCode; | ||
| import com.sniptext.domain.template.repository.TemplateRepository; | ||
| import com.sniptext.domain.template.service.dto.command.TrashRestoreCommand; | ||
| import com.sniptext.domain.template.service.dto.result.TemplateShortcutAssignResult; | ||
| import com.sniptext.domain.user.service.UserCommandService; | ||
| import com.sniptext.global.exception.BusinessException; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class TrashCommandService { | ||
|
|
||
| private final TemplateRepository templateRepository; | ||
|
|
||
| private final UserCommandService userCommandService; | ||
|
|
||
| private final TemplateCommandService templateCommandService; | ||
|
|
||
| /** | ||
| * 휴지통 템플릿을 내 템플릿으로 되돌린다. 한도·자동 번호·저장 규칙은 생성·갤러리 저장과 같다. | ||
| * 휴지통에 없으면 TRASH404_1, 한도 도달이면 TEMPLATE403_1, 번호 2~99가 모두 차면 TEMPLATE409_2 (앞의 것이 먼저다). | ||
| * 회원 잠금이 트랜잭션의 첫 조회여야 한다. MySQL 기본 격리 수준(REPEATABLE READ)은 첫 일반 조회 시점의 스냅샷을 끝까지 쓰므로, | ||
| * 템플릿을 먼저 읽으면 잠금을 기다리는 사이 커밋된 생성·복구를 개수 검사와 단축어 조회가 보지 못한다. | ||
| * 같은 템플릿을 동시에 복구해도 잠금으로 줄을 서므로, 뒤에 온 요청은 이미 꺼낸 템플릿을 읽어 TRASH404_1이 된다. | ||
| * 템플릿 행도 잠가 읽는다. 정리 배치·영구 삭제는 회원을 잠그지 않아, 일반 조회로 읽으면 저장 전에 행이 지워져 500이 나기 때문이다. | ||
| * 잠금 조회는 스냅샷을 만들지 않으므로 이어지는 개수 검사가 옛 값을 보는 문제는 생기지 않는다. | ||
| * 엔티티를 읽으므로 본문 복호화에 실패하는 행은 복구도 500이다. | ||
| */ | ||
| @Transactional | ||
| public TemplateShortcutAssignResult restore(TrashRestoreCommand command) { | ||
| userCommandService.lockUser(command.userId()); | ||
|
|
||
| Template template = templateRepository.findTrashedByIdAndUserIdForUpdate(command.templateId(), command.userId()) | ||
| .orElseThrow(() -> new BusinessException(TrashErrorCode.TRASHED_TEMPLATE_NOT_FOUND)); | ||
|
|
||
| templateCommandService.validateTemplateLimit(command.userId()); | ||
|
|
||
| Shortcut original = template.getShortcut(); | ||
| template.restore(templateCommandService.assignShortcut(command.userId(), original)); | ||
|
|
||
| return TemplateShortcutAssignResult.of(templateCommandService.save(template), original); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 수정 API는 회원을 잠그지 않아서, 거의 안 일어나는 경우라 코드를 바꾸기보다는, 명세 16번·21번 에러 표에 409_1을 "동시 수정과 겹친 경우, 다시 시도하면 됨"으로 추가하고 프론트에 공유하는 정도면 충분할 것 같은데 어떻게 생각하시나요?!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋습니다 해당 부분은 명세 16, 21번에 반영하구 프론트에 공유 드리겠습니다 👏 |
||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package com.sniptext.domain.template.service.dto.command; | ||
|
|
||
| public record TrashRestoreCommand(Long userId, Long templateId) { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(선택, 후속 이슈로 넘겨도 돼요) 한도 검사·자동 번호·저장을 이제 세 곳(생성·갤러리 저장·복구)이 같은 규칙으로 쓰게 됐는데, 지금은
TrashCommandService가TemplateCommandService의 패키지 범위 메서드를 직접 부르는 구조라 서비스끼리 의존하게 됐어요.이 세 메서드를 패키지 범위 컴포넌트(예:
TemplateSaveSupport)로 따로 빼면, 두 서비스가 같은 도우미에 기대는 형태가 되고 "회원 잠금을 먼저 잡은 트랜잭션 안에서 불러야 한다"는 규칙도 한 클래스 주석에 모을 수 있을 것 같아요.동작은 지금도 문제없어서 이번 PR에서 꼭 바꿀 필요는 없다고 생각합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
의견 감사합니당 🙂
확인해 보니 이 세 메서드를 쓰는 곳은 생성, 갤러리 저장, 복구 세 곳으로 끝이고 지금은 같은 패키지 안에서만 부르고
있다보니 괜찮을것 같습니다 !
쓰는 곳이 더 생기거나 TemplateCommandService가 커지면 그때 분리하는것도 좋을것 같아요 !
해당 부분은 남은 API 추가 후 리팩토링 지점으로 두겠습니다