Skip to content

Latest commit

 

History

History
120 lines (88 loc) · 13.2 KB

REFACTORING.md

File metadata and controls

120 lines (88 loc) · 13.2 KB

유지보수를 위한 단계별 리팩토링

기존 기능·권한·성능 최적화를 유지하면서 책임을 분리합니다. 기능 개선, 스키마 변경, 캐시 조정, 라이브러리 교체는 리팩토링 PR과 분리합니다. 검증한 작은 변경을 PR 단위로 검토하고 다음 도메인으로 확장합니다.

단계와 범위

단계 범위 상태 / 완료 조건
1 논문 공개 상태 변경·선택 삭제 API, 화면의 논문 API 호출·공개 상태 표시 PR #1 병합 완료; 기존 권한·쿼리·응답·캐시 계약 보존
2 논문 목록·편집 뷰와 상태 훅 목록·폼·상태·조회/저장 요청 분리; Scholar 가져오기 분리는 후속 단계
3 게시글·고정 본문·연혁·연구 분야 조회와 변경을 나누고 작성자 권한·페이지별 편집·캐시 계약을 먼저 테스트
4 구성원·가입 승인·계정 RPC 트랜잭션·RLS·관리자 보호·가입 복구를 유지; 별도 테스트 DB에서 검증
5 미디어·공통 UI·스타일 private Storage, 직접 전송, 공개 참조 공유 캐시, 리사이징·GIF·반응형 검증 후 정리

1단계는 병합 완료했습니다. 뒤 단계는 아직 전체적으로 수행하지 않았습니다. 사이트 디자인 도메인 분리와 미디어 변환·전송 모듈 분리는 별도 기능 개선 PR에서 진행했으며, 전체 콘텐츠·계정·미디어 계층의 리팩토링 완료를 뜻하지 않습니다. 단계별 변경을 검토하기 전에 전체 도메인을 한 번에 옮기지 않습니다.

1단계의 책임 분리

위치 책임 의존성
app/api/v1/admin/publications/*/route.ts HTTP 경로·메서드 연결 컨트롤러
server/publications/publication.controller.ts 관리자 인증, JSON/DTO 검증, HTTP 응답, 변경 후 캐시 무효화 서비스·리포지토리 조립, Next.js
server/publications/publication.service.ts 공개 상태 변경과 삭제의 처리 순서, 중복 ID 제거, 실제 변경·부분 성공 판단 리포지토리 인터페이스; HTTP·Supabase 런타임에 의존하지 않음
server/publications/publication.repository.ts Supabase 쿼리와 DB 오류 전달 호출자가 제공한 인증된 DB 클라이언트
server/publications/publication.entity.ts 이 유스케이스의 논문 ID·상태 타입, 요약 설정 식별 규칙 외부 의존성 없음
server/publications/publication.dto.ts 요청 스키마와 응답 타입 Zod와 도메인 타입
features/publications/api/publication-client.ts 논문 HTTP 호출·응답·오류 전달 fetch; DTO/엔터티는 타입만 참조
features/publications/components/PublicationStatusControl.tsx 공개 스위치·상태 표시 props만 사용, 조회·변경 요청 없음
components/AdminEditor.tsx 기존 편집 상태·선택·배치 처리·확인창·메시지 조율 위 API 클라이언트와 표시 컴포넌트

엔터티는 필요한 필드만 모델링합니다. DB 행 전체를 억지로 클래스에 감싸거나 실행 시 DI 컨테이너를 추가하지 않습니다. 객체 생성은 컨트롤러에서 하고 서비스는 인터페이스를 주입받습니다. 스키마의 테이블·열 이름과 공개 API 경로는 계약이므로 명시적으로 유지하며, 반복되는 논문 요약 식별 규칙은 한 곳에서 관리합니다. 프론트엔드의 DTO 참조는 import type으로 서버 코드·Zod를 클라이언트 번들에 추가하지 않습니다.

lib/deletion.ts의 기존 논문 삭제 export는 호환 어댑터로 남겨 기존 호출·테스트를 유지합니다. 게시글·구성원 삭제 구현은 이 단계에서 이동하지 않습니다.

보존해야 하는 계약

상황 기존·리팩토링 후 동작
관리자 아님 본문 처리 전 401, requireAdmin() 유지
잘못된 JSON·UUID·상태 / 삭제 ID 0개·101개 이상 400, 기존 한국어 오류 메시지 유지
상태 변경 DB 오류 또는 변경된 행 없음 400; 요약 변경·캐시 무효화 없음
논문 상태 변경 성공, 요약 변경 실패 500; 논문은 변경되었으므로 기존 캐시 태그·경로 무효화
삭제 DB 오류 500; 요약 정리·캐시 무효화 없음
실제 삭제 행 없음 409; 성공으로 처리하지 않음
일부 삭제 실제 삭제 ID만 요약 정리, 200과 기존 warning 반환
요약 정리 실패 삭제 ID 유지, 기존 정리 실패 warning을 우선 반환, 캐시 무효화
정상 변경 lab-public 태그와 / layout 무효화, 기존 응답 모양 유지
프론트엔드 삭제 기존 100개씩 순차 요청, 각 배치 직후 목록·선택 갱신, 경고·실패 처리 유지
공개 스위치 기존 DOM 클래스·접근성 이름·disabled·변경 중 표시 유지

DB 쿼리 수·순서, 인증된 클라이언트/RLS, 응답 코드·메시지, 캐시 TTL·태그, 목록 조회와 동적 에디터 로딩을 바꾸지 않습니다. CSS, 업로드 경로·압축, 이미지 최적화, 공개 미디어 캐시는 이 단계의 변경 대상이 아닙니다.

검증

  • 기존 테스트 57개와 신규 논문 관리 테스트 9개: 총 66개.
  • 신규 테스트: DTO 경계, 상태/요약 처리 순서, DB 오류·0행, 부분 변경·삭제, 중복 ID·실제 삭제 ID 정리, 클라이언트 요청 계약·오류 전달.
  • 기존 논문 삭제 쿼리 테스트는 호환 어댑터를 거쳐 새 리포지토리를 검사합니다.
  • npm run typecheck, npm test, npm run build를 순서대로 실행합니다. 타입 검사와 빌드는 .next/types를 공유하므로 동시에 실행하지 않습니다.
  • 운영 DB 실제 삭제나 가입·탈퇴는 검증 목적으로 실행하지 않습니다. 관리자 로그인 화면의 실제 토글·삭제·뒤로 가기는 별도 운영/테스트 DB 확인 대상입니다.

다음 작업 기준

논문 목록·폼의 상태와 뷰는 아래 2단계 구조로 분리했습니다. 다음 작업은 Scholar 가져오기 또는 게시글 도메인 중 하나를 작은 범위로 선택합니다. AdminEditor의 나머지 도메인을 한 번에 다시 쓰지 않습니다. 공통 훅·기본 저장소·서비스 프레임워크는 실제로 두 도메인 이상에서 공통 책임이 확인된 뒤 도입합니다. 고정값을 무조건 설정 파일로 이동하는 것보다 변경 이유와 소유 도메인을 분명히 하는 데 집중합니다.

운영 반영 시 PR 단위로 되돌릴 수 있습니다. 이 단계에는 DB 마이그레이션이나 새 의존성이 없습니다.

사이트 디자인 도메인

server/theme은 관리자 테마 조회·저장을 담당합니다. 컨트롤러에서 인증·DTO 검사와 캐시 무효화를 수행하고, 서비스는 기본값 읽기 정책, 리포지토리는 인증된 Supabase 클라이언트로 settings/theme 페이지에 접근합니다. lib/theme.ts는 기본 테마·안전한 읽기·CSS 변수·대비 계산을 담당하며, features/theme은 API 호출과 편집 뷰를 분리합니다. 공개 화면은 lib/theme-server.ts와 루트 레이아웃에서 기존 페이지 캐시를 사용해 테마를 전달합니다. 별도의 브라우저 테마 조회나 DB 마이그레이션은 추가하지 않습니다.

기본 SVG는 lib/default-artwork.ts의 저장소 내 정적 허용 목록과 DefaultArtwork 컴포넌트로 렌더링합니다. CSS 변수로 내부 색상을 연결하고 인스턴스마다 그라데이션 ID를 분리합니다. 외부 또는 업로드 이미지는 이 경로에 넣지 않고 기존 이미지 최적화 경로를 유지합니다.

v0.2.0 기준 추가 구조

  • server/theme과 features/theme: 디자인 설정의 인증·검증·조회/저장·미리보기 책임 분리.
  • server/media/image-transform.ts: 정적/애니메이션 변환과 자원 제한·원본 복귀 판단.
  • server/media/media-transfer.ts: 제한된 원본 읽기와 응답 스트리밍.
  • lib/media-variants.ts: 허용 이미지 크기·변환 URL 규칙.
  • 미디어 라우트는 기존 공개 참조·권한 검사를 유지하며 변환 캐시는 권한 검사 이후 읽습니다.

최신 기능 트리의 전체 테스트는 94개입니다. 위 66개 기록은 논문 리팩토링 1단계 당시 결과이며 새 버전 전체 테스트 수와 구분합니다.

논문 편집기 2단계

관리자 대시보드와 공개 논문 페이지의 인라인 편집은 같은 PublicationEditor를 사용합니다. AdminEditor는 기존 호출의 호환 진입점을 유지하며 논문 작업을 전용 편집기로 위임합니다. 게시글·고정 페이지·구성원의 기존 폼은 이번 단계에서 다시 설계하지 않습니다.

위치 책임
features/publications/components/PublicationEditor.tsx 목록·폼·삭제 확인창 조립
PublicationListView.tsx 검색·정렬·선택·공개 상태·행 버튼 표시, 콜백 전달
PublicationFormView.tsx 논문 입력 필드·검증 속성·저장/취소 버튼 표시
features/publications/hooks/usePublicationEditor.ts 초기 조회·저장·삭제·공개 변경과 화면 상태·완료/취소 콜백 조율
features/publications/model/publication-editor.ts 목록/폼/요청 타입, 저자·연도·빈 값 변환, 검색·정렬·삭제 결과 반영
publication-editor-state.ts 편집·초기화·선택·공개 상태·삭제 결과의 순수 상태 전이
delete-publication-batches.ts 100개씩 순차 삭제, 각 성공 배치 반영, 마지막 비어 있지 않은 경고 전달
features/publications/api/publication-client.ts 기존 콘텐츠 조회·POST/PATCH와 논문 공개·삭제 API 요청

다음 동작을 보존합니다.

  • 대시보드에서는 전체 목록을 조회하고, 상세 페이지 인라인 편집에서는 기존 ID 필터를 사용합니다.
  • 제목·저자 검색, 연도·인용수 정렬, 인용수 미상 후순위, 검색 결과 전체 선택을 유지합니다.
  • 수정·새 작성 시 검색·정렬·선택 상태를 유지합니다. Scholar 가져오기 완료 후 목록 재마운트도 유지합니다.
  • 신규 논문은 draft로 시작하며 요약을 입력하지 않았다면 요약 설정을 새로 쓰지 않습니다. 기존 요약은 폼에 유지하고 명시적 빈 값 저장도 전달합니다.
  • 삭제는 기존 API로 100개씩 순차 처리하며 서버가 실제 삭제했다고 반환한 ID만 목록·선택에서 제거합니다. 후속 배치 실패 시 먼저 성공한 삭제 결과를 되돌리지 않고 확인창에 오류를 표시합니다.
  • 저장 후 기존 재조회·인라인 완료 콜백, 취소 콜백, 메시지와 폼 검증 속성·CSS 클래스·공개 스위치 접근성 이름을 유지합니다.
  • 서버 API·DB 쿼리·권한·캐시·이미지 처리·CSS·라이브러리 의존성은 변경하지 않습니다. 논문 편집 화면은 리치 텍스트 에디터를 불러오지 않습니다.

기존 94개에 모델·상태·배치 삭제·조회/저장 계약·React 정적 마크업 회귀 테스트 20개를 추가했습니다. 총 114개 테스트와 타입 검사·프로덕션 빌드로 검증합니다. 정적 마크업 검증은 실제 브라우저·운영 계정에서의 상호작용 검증을 대신하지 않습니다.

콘텐츠 저장의 부분 성공 후 캐시 처리, Scholar 연결 저장 오류 확인, 가입·프로필 처리 분리는 후속 작업입니다. 동작 수정은 책임 분리와 별도 PR로 진행합니다.

미디어 전달 후속 개선 (2026-10-11)

  • visibleMemberRows는 구성원 화면과 미디어 접근 검사가 사용하는 공개 행 조회를 60초 캐시로 공유합니다. 프로필 사진은 게시글·고정 페이지 전체 참조 검색을 생략합니다.
  • 미디어 라우트는 접근 검사와 응답을 조율하고, server/media/derivative-store.ts는 인증된 Storage 스트림 읽기·기존 축소본 조회·변환 결과 저장을 담당합니다. 크기·프레임·용량 검증은 기존 변환/스트림 모듈을 재사용합니다.
  • 이미지 Base64를 Next.js 데이터 캐시에 별도로 보관하지 않습니다. 비공개 원본 소유자 경로 아래에 크기·애니메이션·변환 버전별 WebP를 저장하여 콘텐츠 태그와 루트 레이아웃 무효화에서 독립시킵니다. 첫 응답 후 after로 저장하며, 저장 실패는 사진 표시를 막지 않습니다.
  • 저장된 축소본도 원본에 대한 접근 검사 후에만 읽고, 내부 생성 파일 경로는 공개 미디어 라우트의 허용 경로가 아닙니다. 같은 UUID를 덮어쓰지 않는 업로드 계약을 유지합니다. 생성 파일은 별도 저장 용량과 백업·정리 대상입니다.
  • 서명 URL을 캐시하며 접근을 판별하던 경로는 제거했습니다. 변환/저장 장애 시 원본 스트리밍과 PDF의 기존 권한/리다이렉트 동작은 유지합니다.
  • 후속 검증: 데이터 조회 공유, 최초/기존 변환 파일, 응답 후 저장, 조회/쓰기 오류, 크기 제한, 공개 해제 및 비공개 소유권 차단을 테스트합니다. 실제 운영 로딩 시간은 배포 후 최초 방문과 재방문을 나누어 측정합니다.