개발일지 2026-08-05
음악학원 플랫폼 개발일지 (2026-08-05)
개요
이날은 크게 네 갈래의 작업을 진행했다. (1) 멤버십 구독/휴면(DORMANT) 게이팅으로 미결제 학원의 API 접근을 차단하고, (2) 강사 비활성화/재활성화 + 담당 수강생 재배정 기능을 만들고, (3) 학생 앱의 "학원 변경 신청" 저장 API·화면을 새로 붙이고, (4) 브랜드(공개) 사이트 지점찾기 팝업을 공용 컴포넌트로 통합하면서 공유 방식을 네이버 지도 링크 기반으로 바꿨다. 중간에 학원 SNS 채널(카카오/인스타/유튜브) 필드를 한번 걷어냈다가 다시 살리는 revert 왕복도 있었다.
수정/생성 파일 목록
백엔드 (piano-backend)
.../academy/subscription/AcademySubscriptionController.java(신규) — 구독 요약/상태 조회 엔드포인트.../academy/subscription/AcademySubscriptionService.java(신규) — 구독 상태 계산 로직.../academy/subscription/dto/SubscriptionSummaryResponse.java(신규) — 구독 요약 DTO.../security/DormancyGateFilter.java(신규) — 휴면 학원 API 게이팅 필터.../config/SecurityConfig.java(수정) — 게이트 필터 체인 등록.../academy/notification/AcademyNotiScheduler.java/AcademyNotificationSender.java(수정) — 구독/휴면 알림migration-20260805-academy-subscription-state.sql(신규).../student/academychange/AcademyChangeRequest.java/...Mapper.java/...Service.java/StudentAcademyChangeController.java/dto/AcademyChangeCreate.java(신규) — 학생 학원변경 신청 저장mapper/AcademyChangeRequestMapper.xml(신규),migration-20260805d-academy-change-request.sql(신규).../student/profile/StudentAcademyController.java(수정) — 내 학원 정보 응답에 SNS 필드 추가- SNS 채널 제거/복원 왕복:
Academy.java,AcademyProfileService.java,AcademySignupService.java,LocationMapper.xml,AcademyMapper.xml외 (migration...b-drop-academy-sns.sql→...c-readd-academy-sns.sql)
프론트 (piano-academy 모노레포)
academy/src/api/membership.ts(신규),academy/src/pages/Membership.tsx(대폭 수정),academy/src/api/client.ts(수정) — 멤버십 화면 + 휴면 차단 전역 안내academy/src/api/academyTeachers.ts(수정),academy/src/pages/TeacherDeactivateDialog.tsx(신규),academy/src/pages/Teachers.tsx(수정) — 강사 비활성/활성 + 재배정 팝업student/src/api/academyChangeRequest.ts(신규),student/src/pages/AcademyChangeRequestPage.tsx(신규),student/src/App.tsx/MyPage.tsx(수정) — 학원 변경 신청 화면student/src/components/AcademySnsIcons.tsx(신규),student/src/pages/AcademyPage.tsx(수정) — 내 학원 SNS 아이콘 링크brand/src/components/LocationDetailDialog.tsx(신규 통합),brand/src/components/LocationSnsIcons.tsx(신규),brand/src/pages/Home.tsx/Locations.tsx(대폭 축소) — 지점찾기 팝업 공용 컴포넌트화 + 네이버 링크 공유academy/src/pages/Marketing.tsx,admin/src/pages/MarketingBillings.tsx외 — 결제내역 상세/마케팅 화면 개선
파일별 상세
1. 멤버십 구독/휴면(DORMANT) 게이팅
학원이 멤버십 결제를 안 하면 서비스 이용을 막아야 한다. 서버 사이드에서 이를 강제하기 위해 DormancyGateFilter 를 시큐리티 체인에 추가했다. 로그인 사용자의 학원이 휴면 상태면 화이트리스트(로그인/구독결제/공개 API)를 제외한 요청을 막고, 프론트에는 전용 에러코드를 내려 "휴면 안내 + 결제 유도" 화면으로 유도한다.
// DormancyGateFilter — 휴면 학원은 화이트리스트 외 요청 차단
if (academy.isDormant() && !isWhitelisted(request)) {
writeError(response, ErrorCode.ACADEMY_DORMANT);
return;
}
chain.doFilter(request, response);
AcademySubscriptionService 가 다음 결제일/상태를 계산해 SubscriptionSummaryResponse 로 내려주고, academy 앱의 Membership.tsx 가 이걸 받아 요약 배너 + 결제 유도를 그린다. 휴면 진입/해제 시점에는 AcademyNotificationSender 로 원장에게 알림을 보낸다.
2. 강사 비활성화/재활성화 + 담당 수강생 재배정
강사를 비활성화하면 그 강사가 담당하던 수강생이 붕 뜬다. 그래서 비활성화 시 담당 수강생을 다른 강사에게 재배정하는 절차를 필수로 묶었다. academy 앱에 TeacherDeactivateDialog.tsx 를 만들어, 비활성 대상 강사의 담당 수강생 목록을 보여주고 이관할 강사를 고르게 했다. 반대로 재활성화는 비활성 강사 + 그에 딸린 로그인 계정을 함께 복구한다.
// academyTeachers.ts — 비활성(재배정 포함) / 재활성
export const deactivateTeacher = (id: number, reassignToTeacherId: number) =>
client.post(`/academy/teachers/${id}/deactivate`, { reassignToTeacherId });
export const reactivateTeacher = (id: number) =>
client.post(`/academy/teachers/${id}/reactivate`);
3. 학생 "학원 변경 신청" 저장
학생이 다른 학원으로 옮기고 싶을 때 신청을 넣는 기능이다. 백엔드에 academychange 패키지를 새로 파고(요청 엔티티/매퍼/서비스/컨트롤러/DTO), student 앱에 신청 화면(AcademyChangeRequestPage.tsx)을 붙였다. 이날은 "저장"까지, 관리자 승인 플로우는 이틀 뒤(08-07)에 이어 붙였다.
4. 브랜드 지점찾기 팝업 공용 컴포넌트화 + 공유 방식 개편
메인화면(Home.tsx)과 지점찾기(Locations.tsx)에 각각 흩어져 있던 학원 상세 팝업을 LocationDetailDialog 하나로 통합했다. 코드가 확 줄었다(Locations.tsx −213줄, Home.tsx −100줄). 공유는 카카오 sendScrap 을 시도했다가 이미지 이슈로 여러 번 롤백한 끝에, 결국 자사 사이트 링크 + 네이버 지도 바로가기 아이콘 조합으로 정리했다.
트러블슈팅 메모 (삽질 기록)
- SNS 채널 제거→복원 왕복: 학원 SNS(카카오/인스타/유튜브) 필드를 한 번 전면 제거(
23fdf9d)했다가, 요구가 되살아나 그대로 revert 복원(5d0c5a3)했다. migration 도drop-academy-sns→readd-academy-sns로 왕복. 스키마 변경은 되돌릴 걸 감안해 컬럼 추가/삭제를 별도 migration 파일로 쪼개두니 복원이 깔끔했다. - 카카오 공유 이미지 문제:
sendScrap/feed 카드가 이미지 없이 나오거나 미리보기가 깨져, 최종적으로 네이버 지도 링크로 우회. 공개 사이트 공유는 플랫폼 심사/캐시 이슈가 많아 링크 기반이 안정적이었다.
결론 / 배운 점
- 게이팅은 서버에서 강제해야 의미가 있다. 프론트 안내만으로는 우회 가능하니
DormancyGateFilter로 API 레벨에서 막았다. - 파괴적 액션(강사 비활성)은 후속 정리(수강생 재배정)를 한 트랜잭션/한 UX 흐름으로 묶어야 데이터가 붕 뜨지 않는다.
- 반복되는 팝업 UI 는 공용 컴포넌트로 뽑으면 코드량과 버그가 동시에 줄어든다.
댓글 0
- 첫 번째 댓글을 남겨보세요.