fromis_9/docs/migration.md
caadiq e0ab3ce0f8 fix(backend): getUpcomingSchedules 응답 형식 통일
- getUpcomingSchedules가 getMonthlySchedules와 동일한 날짜별 그룹화 형식 반환
- routes/schedules 응답 스키마에 oneOf 추가 (객체/배열 둘 다 허용)
- docs/architecture.md, migration.md 업데이트

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 20:10:26 +09:00

425 lines
13 KiB
Markdown

# Express → Fastify 마이그레이션
## 개요
`backend-backup/` (Express) → `backend/` (Fastify)로 마이그레이션 완료
## 완료된 작업
### 서버 기반
- [x] Fastify 앱 구조 (`src/app.js`, `src/server.js`)
- [x] 플러그인 시스템 (`src/plugins/`)
- db.js (MariaDB)
- redis.js
- auth.js (JWT)
- meilisearch.js
- scheduler.js (봇 스케줄러)
### API 라우트 (`src/routes/`)
- [x] 인증 (`/api/auth`)
- POST /login - 로그인
- GET /verify - 토큰 검증
- [x] 멤버 (`/api/members`)
- GET / - 목록 조회
- GET /:name - 상세 조회
- PUT /:name - 수정 (이미지 업로드 포함)
- [x] 앨범 (`/api/albums`)
- GET / - 목록 조회
- GET /:id - ID로 조회
- GET /by-name/:name - 이름으로 조회
- GET /by-name/:albumName/track/:trackTitle - 트랙 조회
- POST / - 생성
- PUT /:id - 수정
- DELETE /:id - 삭제
- 사진 관리 (`/api/albums/:id/photos`)
- GET / - 목록
- POST / - 업로드
- PUT /:photoId - 수정
- DELETE /:photoId - 삭제
- 티저 관리 (`/api/albums/:id/teasers`)
- GET / - 목록
- POST / - 업로드
- DELETE /:teaserId - 삭제
- [x] 일정 (`/api/schedules`)
- GET / - 월별 조회 (생일 포함)
- GET /?search= - Meilisearch 검색
- GET /:id - 상세 조회
- DELETE /:id - 삭제
- POST /sync-search - Meilisearch 동기화
- [x] 추천 검색어 (`/api/schedules/suggestions`)
- GET / - 추천 검색어 조회
- GET /popular - 인기 검색어 조회
- POST /save - 검색어 저장
- GET /dict - 사용자 사전 조회 (관리자)
- PUT /dict - 사용자 사전 저장 (관리자)
- [x] 통계 (`/api/stats`)
- GET / - 대시보드 통계
### 관리자 API (`src/routes/admin/`)
- [x] 봇 관리 (`/api/admin/bots`)
- GET / - 봇 목록
- POST /:id/start - 봇 시작
- POST /:id/stop - 봇 정지
- POST /:id/sync-all - 전체 동기화
- GET /quota-warning - 할당량 경고 조회
- DELETE /quota-warning - 할당량 경고 해제
- [x] YouTube 관리 (`/api/admin/youtube`)
- GET /video-info - 영상 정보 조회
- POST /schedule - 일정 저장
- PUT /schedule/:id - 일정 수정
- [x] X 관리 (`/api/admin/x`)
- GET /post-info - 게시글 정보 조회
- POST /schedule - 일정 저장
### 서비스 (`src/services/`)
- [x] YouTube 봇 (`services/youtube/`)
- 영상 자동 수집
- 채널별 필터링 (제목 필터, 멤버 추출)
- [x] X(Twitter) 봇 (`services/x/`)
- Nitter 스크래핑
- 이미지 URL 추출
- [x] Meilisearch 검색 (`services/meilisearch/`)
- 일정 검색
- 전체 동기화
- [x] 추천 검색어 (`services/suggestions/`)
- 형태소 분석 (kiwi-nlp)
- bi-gram 빈도
- 초성 검색
- 사용자 사전 관리
- [x] 이미지 업로드 (`services/image.js`)
- 앨범 커버
- 멤버 이미지
- 앨범 사진/티저
## 남은 작업 (미구현)
### 일반 일정 CRUD
- [ ] POST /api/schedules - 일정 생성 (일반)
- [ ] PUT /api/schedules/:id - 일정 수정 (일반)
※ 현재는 YouTube/X 전용 일정 생성 API만 구현됨
### 카테고리 관리
- [ ] POST /api/schedule-categories - 생성
- [ ] PUT /api/schedule-categories/:id - 수정
- [ ] DELETE /api/schedule-categories/:id - 삭제
- [ ] PUT /api/schedule-categories-order - 순서 변경
※ GET은 구현됨 (목록 조회)
### 기타
- [ ] GET /api/kakao/places - 카카오 장소 검색 프록시
## 파일 비교표
| Express (backend-backup) | Fastify (backend) | 상태 |
|--------------------------|-------------------|------|
| routes/admin.js (로그인) | routes/auth.js | 완료 |
| routes/admin.js (앨범 CRUD) | routes/albums/index.js | 완료 |
| routes/admin.js (사진/티저) | routes/albums/photos.js, teasers.js | 완료 |
| routes/admin.js (멤버 수정) | routes/members/index.js | 완료 |
| routes/admin.js (일정 삭제) | routes/schedules/index.js | 완료 |
| routes/admin.js (일정 생성/수정) | - | 미완료 |
| routes/admin.js (카테고리 CUD) | - | 미완료 |
| routes/admin.js (봇 관리) | routes/admin/bots.js | 완료 |
| routes/admin.js (할당량) | routes/admin/bots.js | 완료 |
| routes/admin.js (카카오) | - | 미완료 |
| - | routes/admin/youtube.js | 신규 |
| - | routes/admin/x.js | 신규 |
| routes/albums.js | routes/albums/index.js | 완료 |
| routes/members.js | routes/members/index.js | 완료 |
| routes/schedules.js | routes/schedules/index.js | 완료 |
| routes/stats.js | routes/stats/index.js | 완료 |
| services/youtube-bot.js | services/youtube/ | 완료 |
| services/youtube-scheduler.js | plugins/scheduler.js | 완료 |
| services/x-bot.js | services/x/ | 완료 |
| services/meilisearch.js | services/meilisearch/ | 완료 |
| services/meilisearch-bot.js | services/meilisearch/ | 완료 |
| services/suggestions.js | services/suggestions/ | 완료 |
## 참고 사항
- 기존 Express 코드는 `backend-backup/` 폴더에 보존
- 마이그레이션 시 기존 코드 참조하여 동일 기능 구현
- DB 스키마 변경 사항:
- `tracks``album_tracks` (이름 변경)
- `venues``concert_venues` (이름 변경)
---
# 프론트엔드 마이그레이션 (Strangler Fig)
## 개요
`frontend/` (레거시) → `frontend-temp/` (신규)로 점진적 마이그레이션
## 설계 원칙
1. **react-device-detect 사용**
- App.jsx에서 `BrowserView`/`MobileView`로 PC/Mobile 완전 분리
- User Agent 기반 디바이스 감지
2. **기능별 폴더 + pc/mobile 하위 폴더**
- 각 페이지 폴더 내에 `pc/`, `mobile/` 서브폴더
- 비즈니스 로직(api, hooks, stores)은 최상위에서 공유
3. **React Query 사용**
- `useQuery`로 데이터 패칭 및 캐싱
4. **관리자 페이지는 PC 전용**
- mobile 폴더 없이 단일 구조
## App.jsx 라우팅 구조
```jsx
import { BrowserView, MobileView } from 'react-device-detect';
function App() {
return (
<BrowserRouter>
<BrowserView>
<Routes>
{/* Admin routes */}
<Route path="/admin" element={<AdminLogin />} />
<Route path="/admin/*" element={<AdminRoutes />} />
{/* Public routes with PC Layout */}
<Route element={<PCLayout />}>
<Route path="/" element={<PCHome />} />
<Route path="/members" element={<PCMembers />} />
<Route path="/album" element={<PCAlbum />} />
<Route path="/album/:name" element={<PCAlbumDetail />} />
...
</Route>
</Routes>
</BrowserView>
<MobileView>
<Routes>
<Route element={<MobileLayout />}>
<Route path="/" element={<MobileHome />} />
<Route path="/members" element={<MobileMembers />} />
...
</Route>
</Routes>
</MobileView>
</BrowserRouter>
);
}
```
## 전체 마이그레이션 체크리스트
### API 계층
#### 공개 API
- [x] client.js (fetchApi, fetchAuthApi)
- [x] albums.js
- [x] members.js
- [x] schedules.js
- [x] auth.js
#### 관리자 API (`api/admin/`)
- [ ] albums.js
- [ ] members.js
- [ ] schedules.js
- [ ] categories.js
- [ ] stats.js
- [ ] bots.js
- [ ] suggestions.js
### 훅 (hooks/)
- [x] useAlbumData.js
- [x] useMemberData.js
- [x] useScheduleData.js
- [x] useScheduleSearch.js
- [x] useCalendar.js
- [x] useAdminAuth.js
- [ ] useToast.js
### 스토어 (stores/)
- [x] useScheduleStore.js
- [x] useAuthStore.js
### 유틸리티 (utils/)
- [x] date.js
- [x] format.js
### 공통 컴포넌트 (components/common/)
- [x] Loading.jsx
- [x] ErrorBoundary.jsx
- [x] Toast.jsx
- [x] Lightbox.jsx
- [ ] LightboxIndicator.jsx
- [ ] Tooltip.jsx
- [ ] ScrollToTop.jsx
### PC 레이아웃 컴포넌트 (components/pc/)
- [ ] Layout.jsx (Outlet 사용)
- [ ] Header.jsx
- [ ] Footer.jsx
### Mobile 레이아웃 컴포넌트 (components/mobile/)
- [ ] Layout.jsx (Outlet 사용)
- [ ] MobileNav.jsx
### 관리자 컴포넌트 (components/admin/)
- [ ] AdminLayout.jsx
- [ ] AdminHeader.jsx
- [ ] ConfirmDialog.jsx
- [ ] CustomDatePicker.jsx
- [ ] CustomTimePicker.jsx
- [ ] NumberPicker.jsx
### 페이지 - Home (pages/home/)
- [ ] pc/Home.jsx
- [ ] mobile/Home.jsx
### 페이지 - Members (pages/members/)
- [ ] pc/Members.jsx
- [ ] mobile/Members.jsx
### 페이지 - Album (pages/album/)
- [ ] pc/Album.jsx
- [ ] pc/AlbumDetail.jsx
- [ ] pc/AlbumGallery.jsx
- [ ] pc/TrackDetail.jsx
- [ ] mobile/Album.jsx
- [ ] mobile/AlbumDetail.jsx
- [ ] mobile/AlbumGallery.jsx
- [ ] mobile/TrackDetail.jsx
### 페이지 - Schedule (pages/schedule/)
- [ ] sections/DefaultSection.jsx
- [ ] sections/XSection.jsx
- [ ] sections/YoutubeSection.jsx
- [ ] pc/Schedule.jsx
- [ ] pc/ScheduleDetail.jsx
- [ ] pc/Birthday.jsx
- [ ] mobile/Schedule.jsx
- [ ] mobile/ScheduleDetail.jsx
### 페이지 - Common (pages/common/)
- [ ] pc/NotFound.jsx
- [ ] mobile/NotFound.jsx
### 페이지 - Admin (pages/admin/) - PC 전용
- [ ] Login.jsx
- [ ] Dashboard.jsx
- [ ] members/List.jsx
- [ ] members/Edit.jsx
- [ ] albums/List.jsx
- [ ] albums/Form.jsx
- [ ] albums/Photos.jsx
- [ ] schedules/List.jsx
- [ ] schedules/Form.jsx
- [ ] schedules/YouTubeForm.jsx
- [ ] schedules/XForm.jsx
- [ ] schedules/YouTubeEditForm.jsx
- [ ] categories/List.jsx
- [ ] bots/Manager.jsx
- [ ] dict/Manager.jsx
### 기타
- [ ] App.jsx (BrowserView/MobileView 라우팅)
- [ ] main.jsx
### CSS 파일
- [x] index.css
- [ ] mobile.css (모바일 전용 스타일, 달력 등)
- [ ] pc.css (PC 전용 스타일)
### 기타 파일
- [ ] data/dummy.js (개발용 더미 데이터)
- [ ] .env (VITE_KAKAO_JS_KEY - 콘서트 장소 검색용, 미구현)
- [ ] public/favicon.ico
## 사용 라이브러리 (package.json)
| 라이브러리 | 용도 | 사용 위치 |
|-----------|------|----------|
| react-device-detect | PC/Mobile 분기 | App.jsx |
| @tanstack/react-query | 데이터 패칭 | 모든 페이지 |
| @tanstack/react-virtual | 가상화 리스트 | 관리자 일정 목록 |
| react-calendar | 캘린더 | 일정 페이지 |
| react-colorful | 색상 선택 | 카테고리 관리 |
| react-photo-album | 앨범 갤러리 | 앨범 갤러리 |
| react-infinite-scroll-component | 무한 스크롤 | 일정 검색 |
| react-intersection-observer | 뷰포트 감지 | 애니메이션 |
| react-ios-time-picker | 시간 선택 | 일정 폼 |
| react-linkify | URL 자동 링크 | 일정 상세 |
| react-window | 가상화 리스트 | 긴 목록 |
| swiper | 슬라이더 | 앨범 상세 |
| canvas-confetti | 축하 효과 | 생일 페이지 |
| framer-motion | 애니메이션 | 전체 |
| dayjs | 날짜 처리 | 전체 |
| zustand | 상태 관리 | 전체 |
## 특수 패턴 및 주의사항
### React Query 고급 사용
- `useInfiniteQuery` - 일정 검색 무한 스크롤 (mobile/Schedule)
- `useQuery` - 일반 데이터 패칭
### Swiper 사용 시
```jsx
import { Swiper, SwiperSlide } from 'swiper/react';
import { Virtual } from 'swiper/modules';
import 'swiper/css';
```
- 사용 위치: mobile/Members, mobile/AlbumDetail, mobile/AlbumGallery
### 가상화 리스트
- `useVirtualizer` from `@tanstack/react-virtual`
- 사용 위치: mobile/Schedule, admin/AdminSchedule
### 교차 관찰자
- `useInView` from `react-intersection-observer`
- 사용 위치: mobile/Schedule (무한 스크롤 트리거)
### 축하 효과
- `canvas-confetti` - 생일 페이지 폭죽 효과
- 사용 위치: pc/Birthday, mobile/Schedule (생일 일정)
### 색상 선택기
- `HexColorPicker` from `react-colorful`
- 사용 위치: admin/AdminScheduleCategory
### 앨범 갤러리
- `RowsPhotoAlbum` from `react-photo-album`
- 사용 위치: pc/AlbumGallery, mobile/AlbumGallery
### Kakao API (미구현)
- 콘서트 장소 검색: `/api/admin/kakao/places`
- 환경변수: `VITE_KAKAO_JS_KEY`
- 사용 예정: 콘서트 일정 추가 시 장소 검색
## 마이그레이션 진행 상황
### 완료된 작업 (재검토 필요)
- [x] Phase 1: 프로젝트 셋업
- [x] Phase 2: 유틸리티 및 상수
- [x] Phase 3: Zustand 스토어
- [x] Phase 4: API 계층 (공개 API만)
- [x] Phase 5: 커스텀 훅 (일부)
- [x] Phase 6: 공통 컴포넌트 (일부)
### 구조 리팩토링 필요
- [ ] 기존 코드를 새 폴더 구조로 재배치
- [ ] App.jsx를 BrowserView/MobileView 구조로 변경
- [ ] 레이아웃 컴포넌트 분리 (components/pc, components/mobile)
### 미완료 작업
- [ ] 관리자 API 전체
- [ ] 관리자 컴포넌트 전체
- [ ] 관리자 페이지 전체
- [ ] 누락된 공통 컴포넌트 (LightboxIndicator, Tooltip, ScrollToTop)
- [ ] 누락된 페이지 (AlbumDetail, AlbumGallery, TrackDetail, ScheduleDetail, Birthday)
- [ ] 누락된 훅 (useToast)
- [ ] PC/Mobile 페이지 분리
### 최종 검증
- [ ] 모든 라우트 동작 확인
- [ ] PC/Mobile 전환 테스트
- [ ] 관리자 기능 테스트
- [ ] frontend-temp → frontend 교체