Skip to content

[Feat/#132] 사물함 구역 목록·상세 관리자 API 추가 - #141

Open
sangrae2325 wants to merge 3 commits into
fix/#128-locker-apply-period-validationfrom
feat/#132-admin-locker-sections
Open

sangrae2325 wants to merge 3 commits into
fix/#128-locker-apply-period-validationfrom
feat/#132-admin-locker-sections

Conversation

@sangrae2325

Copy link
Copy Markdown
Collaborator

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

운영진 콘솔에서 사물함 구역을 조회하는 관리자 API 2개를 추가합니다.

  • GET /v1/admin/lockers/sections?lockerPeriodId= — 구역 목록 + 구역별 전체·선택 가능·배정 수
  • GET /v1/admin/lockers/sections/{sectionId}?lockerPeriodId= — 칸 배치·사진·사물함별 상태

#119(운영 회차)에 이어지는 사물함 관리자 API의 두 번째 작업이고, 회차를 입력으로 받는 첫 소비자입니다.

❓ 왜 해결해야 하나요?

구역·사물함을 볼 방법이 학생 앱뿐입니다. 운영진이 배정 현황을 보려면 DB를 직접 봐야 합니다.

그리고 구역 사진이 한 장만 저장됩니다. V15가 구역당 한 장을 전제로 photo_file_id 단일 컬럼을 뒀는데, 실제로는 사진 두 장을 띄우는 구역이 있습니다. 학생 앱도 여러 장을 보여줘야 해서 이 PR에서 함께 고칩니다.

⭐ 어떻게 해결했나요?

커밋 3개로 나눴고 각각 단독으로 컴파일됩니다.

1. 구역 사진 여러 장 지원

photo_file_id → photo_file_ids JSON으로 바꿉니다(V21). notices.image_ids·events.image_ids 등 네 컬럼이 이미 쓰는 방식 그대로 @JdbcTypeCode(SqlTypes.JSON) + List<Long>입니다. 별도 테이블을 쓰는 곳은 저장소에 없어서 그쪽이 오히려 새 패턴이 됩니다.

학생 앱 응답도 photoUrl(문자열) → photoUrls(배열)로 바꿉니다. #98이 URL 하나만 주던 모양을 그대로 복수화했습니다.

2. 구역 목록에 배정 수 추가

LockerSectionSummary에 assignedCount를 더합니다. getSections가 이미 신청 식별자를 들고 있어 세는 건 한 줄입니다. 학생 응답은 이 값을 읽지 않아 그대로입니다.

3. 관리자 API

LockerService를 하나도 고치지 않고 기존 getSections·getSectionDetail·getAppliedLockerIds를 그대로 씁니다. 구역은 회차에 묶여 있지 않고(locker_sections에 locker_period_id가 없습니다) 회차는 "어느 회차의 배정 현황을 볼지"를 정하는 기준일 뿐이라, 학생 경로와 읽는 것이 같습니다.

사물함 표시 상태(AVAILABLE/DISABLED/ASSIGNED)는 저장된 물리 상태와 회차 배정 여부를 합친 값이라 응답 레코드 안 중첩 enum으로 뒀습니다(coding-style.md 2-2절). #65 리뷰에서 "표시용 boolean은 api 모듈에서 조합하라"고 받은 지적을 따랐습니다.

🧩 이 PR의 한계 & 트레이드오프

  • Layout 레코드가 학생 응답과 12줄 중복입니다. app-api와 admin-api는 서로 의존하지 않고, common-api로 올리면 공유 계층이 core:domain:event에 의존하게 됩니다. 모듈 경계상 피할 수 없다고 보고 복제했습니다
  • LockerPeriodParams가 app-api의 같은 이름 레코드와 중복입니다. 필드 하나라 @RequestParam으로 받으려 했는데, GlobalExceptionHandler가 MissingServletRequestParameterException을 다루지 않아 값이 빠지면 500이 납니다. 400으로 내보내려면 @Valid @ModelAttribute가 필요해 DTO로 뒀습니다. (저장소의 기존 @RequestParam 10곳은 전부 required = false라 이 경로를 밟은 적이 없습니다. 핸들러 보강은 별도 이슈가 맞다고 봅니다)
  • 구역 순서는 식별자 오름차순입니다. 명세는 "노출 순서"인데 locker_sections에 순서 컬럼이 없습니다. 라벨이 A-1·A-2·B-1이라 지금은 ID 순 = 라벨 순이고, 중간에 구역을 추가해 어긋나면 그때 display_order를 둡니다
  • 사진 "최소 한 장"은 DB가 못 지킵니다. JSON NOT NULL로 둬도 빈 배열이 통과합니다. 최소 한 장 검증은 구역 쓰기 API에서 @Size(min = 1)로 합니다
  • 테스트 코드는 없습니다. AGENTS.md 원칙에 따랐습니다

⛓️ 기존 기능에 미치는 영향

  • 학생 앱 구역 상세 응답이 바뀝니다 — photoUrl(문자열) → photoUrls(배열). 사진 파일이 삭제된 경우 기존에는 null이었는데 이제 목록에서 빠집니다. 앱이 아직 API 연동 전이라(뷰 퍼블리싱만) 실제 영향은 없습니다
  • V21이 photo_file_id를 DROP COLUMN 합니다. 되돌릴 수 없습니다. dev locker_section_layouts가 0건이라 백필이 건드릴 행도 없습니다
  • LockerSectionLayout.photoFileId → photoFileIds로 바뀌어 엔티티와 학생 컨트롤러가 함께 바뀝니다
  • LockerSectionSummary에 필드가 하나 늘지만 구성 지점이 서비스 한 곳뿐이라 파급이 없습니다
  • 관리자 API는 전부 신규라 기존 경로에 영향이 없습니다

🔀 Edge Case & 실패 시나리오

상황 동작
없는 회차로 조회 LOCKER_PERIOD_NOT_FOUND (404)
lockerPeriodId 누락 INVALID_INPUT (400) + "사물함 운영 회차를 입력해 주세요."
없는 구역 상세 조회 LOCKER_SECTION_NOT_FOUND (404)
배치 구조 미등록 구역 layout이 null, photos가 빈 배열
사진 파일이 삭제됨 그 항목만 photos에서 빠짐
사물함이 한 대도 없는 구역 목록에 남고 세 수가 전부 0
배정된 뒤 사용 중지된 칸 상세는 ASSIGNED, 목록의 assignedCount에도 포함

마지막 줄이 명세에 없어 정한 부분입니다. 운영진이 그 학생에게 다른 자리를 줘야 하는 상황이라 배정 사실이 물리 상태보다 먼저 보여야 한다고 봤습니다. 그래서 totalCount - availableCount - assignedCount가 0이 아닐 수 있습니다(배정 없이 사용 중지된 칸).

📋 검토한 대안과 선택 이유

  • 사진을 locker_section_photos 별도 테이블로: 정석이지만 저장소에 그 패턴이 없고, 파일 목록은 전부 JSON 컬럼입니다. 노출 순서도 배열 순서로 표현됩니다
  • photos를 사진 없을 때 null로: 명세 설명문은 null, 필드 표는 nullable 아님으로 서로 다릅니다. 클라이언트가 null 체크를 안 하도록 빈 배열로 통일했습니다
  • 학생 응답도 photos: [{fileId, fileUrl}]로: #98이 URL 문자열만 주고 있었고 학생 앱은 보여주기만 해서 photoUrls: [String]로 뒀습니다. 관리자는 사진을 지우거나 순서를 바꿀 때 fileId가 필요해 명세대로 객체 배열입니다
  • 표시 상태를 도메인 enum으로: #65에서 같은 성격의 VO(LockerAvailability)를 도메인에서 걷어낸 적이 있습니다. 응답 중첩 enum으로 뒀습니다
  • 기존 LockerStatus에 ASSIGNED 추가: lockers.status에 저장되는 값이라 DB에 없는 파생값을 섞으면 안 됩니다
  • 관리자용 조회 메서드 분리: 구역 관리 화면이 가장 최근 회차만 보고 그 회차는 항상 게시 상태라, 기존 메서드를 그대로 썼습니다

💬 리뷰 포인트

  • [c] Layout 레코드 중복 — 모듈 경계상 불가피하다고 봤는데 다른 방법이 있다면
  • [c] LockerPeriodParams 복제 — @RequestParam의 500 문제 때문인데, GlobalExceptionHandler 보강을 별도 이슈로 먼저 할지
  • [a] 배정된 뒤 사용 중지된 칸을 ASSIGNED로 본 판단
  • [a] 구역 순서를 식별자 오름차순으로 둔 것

검증 범위

compileJava·compileTestJava, :core:domain:event:test, :infrastructure:db:test(Testcontainers — V21 적용과 엔티티 스키마 검증 포함), ModularityTests.verify()·DomainImplAccessTests 통과했습니다.

@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: billilge/stream-server/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: aa186449-b530-4b34-a8fe-5ec9c8f2e89e
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

사물함 구역 목록·상세 관리자 API 추가

1 participant