사이트픽 블로그
← 목록으로

개발일지 2026-07-23

피아노 학원 플랫폼 개발일지 (2026-07-23)

개요

오늘은 피아노 학원 플랫폼에서 세 가지 작업을 진행했다.

  1. 악보 출력 권한 확대 — 기존에는 원장(OWNER)만 악보를 출력할 수 있었는데, 학생(STUDENT)도 출력 가능하도록 넓혔다. 단, 강사(TEACHER)는 여전히 열람/미리보기만 가능.
  2. 레슨 차단 다이얼로그 UI 개선 — 학원 스케줄의 "레슨 차단/추가 오픈" 팝업 아이콘·문구·버튼 스타일 정리.
  3. 전체 일정 날짜 네비게이션 개선 — 날짜 텍스트 세로 정렬이 어긋나던 문제를 고치고, 요일·상대날짜(오늘/이번 주 등) 보조정보를 추가.

수정/생성 파일 목록

백엔드 (piano-backend)

  • .../student/sheetsong/StudentSheetSongController.java (수정) — 악보 출력 /file 엔드포인트 권한을 OWNER → OWNER+STUDENT 로 확대(TEACHER 제외)

프론트 (piano-academy)

  • sheets/src/app/songs/page.tsx (수정) — 악보 상세 canPrint 로직을 OWNER+STUDENT 로 확대, 알림 문구·주석 수정
  • academy/src/components/schedule/OverrideDialog.tsx (수정) — 레슨 차단 다이얼로그 아이콘·문구·버튼 스타일 개선
  • academy/src/pages/Schedule.tsx (수정) — 날짜 네비 세로 정렬 수정 + 요일·상대날짜 보조정보 추가

파일별 상세

1. 악보 출력 권한 확대 (원장 → 원장+학생)

mforet.kr/sheets 악보 상세 화면에서, 기존에는 "출력하기" 버튼이 원장에게만 노출됐다. 요구사항은 학생도 출력 가능, 강사는 열람/미리보기만.

프론트만 바꾸면 서버 /file 엔드포인트가 여전히 OWNER만 허용해 학생이 403을 받기 때문에, 백엔드와 프론트를 함께 손봤다.

백엔드 — 엔드포인트 권한 확대

StudentSheetSongController.java 의 출력 엔드포인트에서 역할 체크를 넓혔다.

// 변형 출력용 PDF 바이너리 (원장·학생 + 이력). 강사(TEACHER)는 불가.
@PostMapping("/items/{itemId}/file")
public ResponseEntity<byte[]> file(@AuthenticationPrincipal UserPrincipal principal,
                                   @PathVariable Long itemId) {
    Long userId = StudentScope.requireUserId(principal);
    if (principal.getRole() != Role.OWNER && principal.getRole() != Role.STUDENT) {
        throw new BusinessException(ErrorCode.FORBIDDEN);
    }
    StorageService.ObjectContent content = service.printItemBytes(userId, itemId);
    return pdfResponse(content.bytes(), userId, false);
}

열람용 /view 엔드포인트는 원래부터 로그인한 모든 역할이 사용 가능하므로 그대로 뒀다. 즉 강사는 /view(워터마크 합성 열람)와 미리보기만 되고, 출력용 /file은 403.

프론트 — canPrint 로직

sheets/src/app/songs/page.tsx 에서 출력 버튼 노출 조건을 확대했다.

// 출력(프린트)은 원장(OWNER)·학생(STUDENT)만 가능. 강사(TEACHER)에게는 출력 버튼을 노출하지 않는다.
const canPrint = user?.role === 'OWNER' || user?.role === 'STUDENT';

더불어 출력 불가 알림 문구도 '악보 출력은 원장·학생 계정만 이용할 수 있습니다.' 로 바꿨다.

프론트에서 버튼을 숨겨도 서버가 최종 방어선이므로, 강사가 우회 호출해도 403으로 막힌다.

2. 레슨 차단 다이얼로그 UI 개선

academy/src/components/schedule/OverrideDialog.tsx — 스케줄에서 "레슨 차단 / 추가 오픈"을 지정하는 팝업의 UI를 정리했다.

  • 제목 아이콘: 차단이면 Block 아이콘, 오픈이면 Circle 아이콘으로 구분
  • 제목 문구: "레슨 차단" → "레슨 차단 시간 지정" 으로 명확화
  • 하단 버튼: 취소는 outlined, 실행 버튼은 차단(검정)/오픈(초록) 색상으로 구분하고 여백 정리

3. 전체 일정 날짜 네비게이션 개선

academy/src/pages/Schedule.tsx — "전체 일정" 화면 상단의 날짜 네비게이션(< 오늘 > 07-20 ~ 07-26)에서 두 가지를 손봤다.

(1) 세로 정렬 어긋남 수정

날짜 텍스트 Typography 에 세로 정렬 속성이 없어, 옆의 아이콘 버튼/"오늘" 버튼과 baseline이 어긋나 텍스트만 위로 떠 보였다. inline-flex + alignItems: center + lineHeight: 1 로 수직 중앙 정렬을 맞췄다.

(2) 요일·상대날짜 보조정보 추가

날짜만 덩그러니 있어 심심했던 영역을 2줄 구조로 개선했다. 메인 날짜 아래에 상대 정보를 작은 글씨로 붙이고, 현재(오늘/이번 주)면 primary 색으로 강조.

// 자정 기준 두 날짜 사이의 일수 차이 (b - a)
const dayDiff = (a: Date, b: Date) => {
  const x = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime();
  const y = new Date(b.getFullYear(), b.getMonth(), b.getDate()).getTime();
  return Math.round((y - x) / 86400000);
};

// 일간 뷰: 오늘/내일/어제/N일 후·전
const daySubInfo = (d: Date) => {
  const diff = dayDiff(new Date(), d);
  if (diff === 0) return '오늘';
  if (diff === 1) return '내일';
  if (diff === -1) return '어제';
  return diff > 0 ? `${diff}일 후` : `${-diff}일 전`;
};

// 주간 뷰: 'M월 W주차 · 이번 주/다음 주/N주 후·전'
const weekSubInfo = (weekStart: Date) => {
  const thisMon = mondayOf(new Date());
  const weeks = Math.round(dayDiff(thisMon, weekStart) / 7);
  const rel = weeks === 0 ? '이번 주' : weeks === 1 ? '다음 주' : weeks === -1 ? '지난 주'
    : weeks > 0 ? `${weeks}주 후` : `${-weeks}주 전`;
  // 해당 주 목요일 기준으로 월/주차 산정(월 경계 흔들림 완화)
  const thu = new Date(weekStart); thu.setDate(thu.getDate() + 3);
  return `${thu.getMonth() + 1}월 ${Math.ceil(thu.getDate() / 7)}주차 · ${rel}`;
};

주차 산정에서 weekStart(월요일) 그대로 쓰면 월 경계에서 흔들리므로, 해당 주 목요일 기준으로 월/주차를 계산해 ISO 주차와 유사하게 안정화했다.


검증 & 배포

세 작업은 성격에 맞춰 나눠 배포했다.

  • 악보 권한: 백엔드는 좀비 JVM 방지 절차(stop → kill → 포트 free 확인 → jar 교체 → start)로 무중단-안전 재시작. 재기동 후 ping=200, auth/me=401(hang 없음), HikariPool 정상 확인. sheets 앱은 out/ 정적 export를 /var/www/piano/sheets/ 에 배포(_next 교체 + chown nginx). /sheets/songs/?id=17 → 200.
  • 스케줄 다이얼로그 / 날짜 네비: academy 앱 빌드 후 /var/www/piano/academy/ 에 배포, chown nginx. https://mforet.kr/academy/ → 200. 배포 번들 해시가 로컬 빌드와 일치하는지 매번 확인.

각 변경은 back.git / front.git 에 커밋·푸시 완료.


트러블슈팅 메모 (삽질 기록)

  • 날짜 정렬 문제 원인 파악: 스크린샷만으로는 "오늘"과 날짜 텍스트의 정렬이 미묘하게 어긋난 걸 캐치하기 어려웠다. MUI Typography(body2)의 line-height와 버튼 높이 차이가 원인. Stack alignItems="center" 만으로는 텍스트 baseline까지 맞지 않아, 텍스트 자체를 inline-flex 아이템으로 만들어 해결.
  • 로컬 실시간 검증의 한계: 브라우저로 academy 화면을 직접 확인하려 했으나 로그인 벽에 막혔다. 자격증명을 임의 입력하지 않는 원칙에 따라, 브라우저 확인 대신 빌드 통과 + 코드 리뷰로 검증을 마무리했다.
  • base URL 함정: academy 앱은 dev 서버 base가 /academy/ 라서 localhost:3002/schedule 로 접근하면 안내 페이지가 뜬다. localhost:3002/academy/schedule 로 접근해야 한다.

결론 / 배운 점

  • 권한 변경은 프론트+백엔드를 세트로: 버튼 노출(프론트)만 바꾸면 서버가 최종 방어선에서 막아 403이 난다. 역할 확대는 반드시 서버 엔드포인트 권한까지 함께 손봐야 한다.
  • 주차 계산은 목요일 기준으로: 주(월~일)가 두 달에 걸칠 때 월/주차가 흔들리는 걸, ISO 방식처럼 해당 주 목요일을 기준으로 잡으면 깔끔하게 안정화된다.
  • 작은 UI 정보 하나가 UX를 바꾼다: 날짜 옆 "오늘 / 이번 주" 한 줄만 붙여도 현재 위치 파악이 훨씬 쉬워진다.

댓글 0

  • 첫 번째 댓글을 남겨보세요.