Repository navigation
[Feat/#186] 아카이빙 등록·수정·삭제·노출 변경 관리자 API 추가 - #196
Open
sangrae2325 wants to merge 6 commits into
Open
sangrae2325 wants to merge 6 commits into
sangrae2325 wants to merge 6 commits into
Conversation
@SiZe는 null을 통과시켜 archiveIds를 빠뜨린 요청이 NPE로 500이 났다. @notempty를 함께 걸어 400 INVALID_INPUT으로 나가게 한다. 같은 PR의 imageIds는 이미 @notempty를 쓰고 있어 기준이 갈려 있었다. 함께 정리한 것 - 중복 식별자 제거를 요청에서 서비스로 옮겨, 스스로 보장하는 파일 존재 확인과 기준을 맞춘다 - 등록·수정 Command가 필드까지 같아 ArchiveWriteCommand 하나로 합친다 - ArchiveJpaEntity의 @DynamicUpdate 주석을 바로잡는다. 수정은 같은 트랜잭션에서 엔티티를 올린 뒤 merge하므로 is_deleted가 변경으로 잡히지 않아 UPDATE에서 빠진다 — 삭제가 되돌아가지 않는다 - import 순서 정리
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
github-actions
Bot
requested review from
jjunh33,
leegain1,
tnals0924 and
xeoxxn
October 29, 2026 14:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
운영진 콘솔에서 아카이빙을 등록·수정·삭제하고, 슬랑제 페이지 노출 여부를 한 번에 바꾸는 관리자 API 4개를 추가합니다.
아카이빙 관리자 API의 마지막 작업입니다.
❓ 왜 해결해야 하나요?
지금은 아카이빙을 넣을 방법이 DB 직접 수정뿐입니다. #40이 학생 앱 조회만 만들었고
archive도메인에 쓰기가 하나도 없습니다. #188이 조회와 노출·삭제 컬럼을, #191이 행사 연결을 끝냈으니 이 PR이 그 위에 쓰기를 올립니다.노출 변경은 목록의 체크박스 일괄 변경과 행별 드롭다운 변경에 같은 엔드포인트를 씁니다 — 행별 변경은 식별자가 하나인 요청입니다.
⭐ 어떻게 해결했나요?
커밋 6개로 나눴고 각각 단독으로 컴파일됩니다. 마이그레이션은 없습니다 — #188(V27)과 #191(V28)이 컬럼을 모두 넣습니다.
1. 도메인 쓰기 —
Archive.create·update·changeExposure와ArchiveWriteCommand.등록과 수정이 담는 값이 같아 Command를 하나로 둡니다. 수정이 전체 필드를 통째로 바꾸는 방식이라 등록과 모양이 같고, 둘로 나누면 같은 선언이 두 벌 생깁니다. 요청 record도 같은 이유로 하나입니다.
2. 저장소 쓰기 경로 —
save·saveAll·delete·findAllByIds와@DynamicUpdate.삭제는
NoticeRepositoryImpl.delete와 같이 관리 상태 엔티티를 읽어 표시만 바꿉니다. 더티 체킹이is_deleted한 컬럼만 UPDATE합니다.3~4. 서비스·API —
AdminArchiveService에 네 동작, 사진·행사 확인은AdminArchiveWriteUseCase가 조합합니다(architecture.md6-1절).검증을 네 갈래로 나눴습니다
INVALID_INPUTArchive.validatePeriodINVALID_ARCHIVE_PERIODAdminArchiveWriteUseCaseFILE_NOT_FOUND/EVENT_NOT_FOUNDAdminArchiveServiceARCHIVE_NOT_FOUND기간 검증만 요청 어노테이션으로 표현할 수 없습니다 — 값 하나의 형식이 아니라 두 값 사이의 관계라서요.
🧩 이 PR의 한계 & 트레이드오프
@DynamicUpdate에 기대고 있습니다. 수정이 같은 트랜잭션에서findById로 엔티티를 영속성 컨텍스트에 올린 뒤 merge하므로, 도메인이 들고 있지 않은is_deleted는 읽어 둔 값 그대로여서 변경으로 잡히지 않고 UPDATE에서 빠집니다. 수정 경로가 "먼저 읽고 나중에 저장"하는 한 유효하고, 읽지 않고 바로 merge하는 경로가 생기면 깨집니다archiveId를 돌려줘야 합니다Archive.thumbnailFileId()가 "사진 배열의 첫 장이 대표"라서, 비우면 학생 앱 목록에서 썸네일이 빈 칸이 됩니다. 비우고 싶다는 요구가 나오면 다시 봅니다PageParams의size상한)와 같게 뒀습니다DELETE에 숫자가 아닌archiveId를 넣으면 500입니다.@PathVariable Long이MethodArgumentTypeMismatchException을 내는데GlobalExceptionHandler에 핸들러가 없습니다. 저장소 전체 공통 문제라common-api를 건드려야 해서 범위에서 뺐습니다. 후속 이슈로 보면 좋겠습니다@Transactional을 걸지 않았습니다. 확인은 읽기이고 그 전에 쓰는 것이 없어 실패해도 되돌릴 것이 없습니다. 쓰기는 서비스가 자기 트랜잭션에서 합니다(AdminNoticeWriteUseCase·AdminLockerAssignUseCase와 같은 판단)AGENTS.md원칙에 따랐습니다⛓️ 기존 기능에 미치는 영향
is_deleted필터가 이 PR 전에는 켤 방법이 없어 확인할 수 없었습니다ArchiveErrorCode에INVALID_ARCHIVE_PERIOD하나가 늘고, 기존 코드는 그대로입니다ArchiveJpaEntity에@DynamicUpdate가 붙습니다. 쓰기 경로가 처음 생기는 자리라NoticeJpaEntity·LockerJpaEntity와 같은 이유로 답니다🔀 Edge Case & 실패 시나리오
imageIds가 빈 배열이거나 누락INVALID_INPUT(400)archiveIds누락INVALID_INPUT(400) —@Size만으로는 null이 통과해 500이 나던 것을@NotEmpty로 막았다archiveIds가 101건INVALID_INPUT(400)archiveIds에 같은 값이 두 번endDate가startDate보다 앞섬INVALID_ARCHIVE_PERIOD(400)endDate생략FILE_NOT_FOUND(404)EVENT_NOT_FOUND(404)ARCHIVE_NOT_FOUND(404)ARCHIVE_NOT_FOUND(404), 아무것도 바뀌지 않는다null)📋 검토한 대안과 선택 이유
LockerPeriodCreateCommand/UpdateCommand는 실제로 필드가 달랐습니다(수정에 연도·학기가 없음)LockerStatusChangeRequest.distinctLockerIds()선례가 있지만, 같은 PR의 파일 존재 확인이Set.copyOf로 스스로 보장하고 있어 기준을 맞춰 서비스로 옮겼습니다PUT으로: 한 필드만 바꾸는 부분 변경이라PATCH가 맞습니다isExposed로 충분하고, 어디에 노출되는지는 주석과 javadoc이 설명합니다. 컬럼·경로·필드를 스택 전체에서 같은 기준으로 맞췄습니다💬 리뷰 포인트
[c]@DynamicUpdate가 삭제 되살아남을 막는 근거가 "수정 경로가 먼저 읽는다"에 걸려 있습니다. 지금update는findById→save순서라 안전한데, 이 전제가 코드로 강제되지는 않습니다. 더 단단히 가려면Archive가 삭제 여부를 들고 있거나BaseSoftDeleteEntity를 손봐야 하는데 둘 다 이 PR 범위를 넘습니다[c]사진 최소 한 장이 맞는지 봐주세요. 명세는 선택인데 대표 이미지가 사진 배열의 첫 장이라 강제하는 쪽으로 갔습니다[a]경로·필드 이름이 명세와 다릅니다.seulrangje-exposure→/exposure,isSeulrangjeExposed→isExposed. 스택 전체를 같이 바꿨고 이슈 본문도 맞췄는데, 관리자 프론트가 명세를 보고 붙였다면 함께 맞춰야 합니다