Files
chocoadmin/docs/plan/project-plan.md
T

410 lines
15 KiB
Markdown

# choco-admin 프로젝트 계획서
## 1. 프로젝트 개요
| 항목 | 내용 |
|---|---|
| 서비스명 | choco-admin |
| 목적 | chocomae 웹 서비스(mouse-typing)의 관리자 기능을 독립 웹 서비스로 분리 |
| 기존 서비스 경로 | `/Users/jisangs/work/jinaju/git/web/mouse-typing` |
| 신규 서비스 경로 | `/Users/jisangs/work/jinaju/git/web/choco-admin` |
| 기존 운영 환경 | AWS EC2 t3a.medium 인스턴스 1개 |
| 사용 DB | 기존 chocomae DB (MariaDB 10.11.13) 그대로 사용 |
---
## 2. 핵심 개발 방향
1. 기존 `chocomae` DB를 그대로 사용한다. DB 스키마는 임의로 변경하지 않는다.
2. 관리자 UI와 서버 로직만 신규 Next.js 서비스로 분리한다.
3. 기존 테이블 구조와 상태값을 그대로 존중하고, 기존 서비스의 비즈니스 규칙을 먼저 파악한 뒤 동일하게 구현한다.
4. 승인·취소·거절 같은 변경 액션은 반드시 서버 사이드에서 권한 검증 → 트랜잭션 → 로그 기록 순서로 처리한다.
5. 중복 처리 방지를 위해 모든 변경 API는 현재 상태를 조건으로 먼저 검사한다.
---
## 3. 기술 스택
| 구분 | 기술 |
|---|---|
| 런타임 | Node.js LTS |
| 프레임워크 | Next.js 16 (App Router) |
| 언어 | TypeScript |
| 패키지 매니저 | pnpm |
| UI / CSS | Tailwind CSS + shadcn/ui |
| Table | TanStack Table (React Table v8) |
| DB 클라이언트 | mysql2 (MariaDB 호환) |
| ORM | Prisma (기존 DB introspection 방식으로 활용) |
| 유효성 검증 | Zod |
| 인증 | NextAuth.js v5 (기존 `admin` 테이블 활용) |
| 환경변수 관리 | `.env.local` / `.env.production` |
### Prisma vs Drizzle ORM
두 가지 모두 MariaDB를 지원하나, 이 프로젝트에서는 **Prisma**를 우선 사용한다.
- 기존 DB를 `prisma db pull`(introspection)로 그대로 가져올 수 있어 초기 세팅이 빠름
- TypeScript 타입 자동 생성으로 컬럼명 오타를 컴파일 단계에서 잡을 수 있음
- 세밀한 SQL 제어가 필요한 구간은 `$queryRaw` / `$executeRaw`로 raw SQL 병행 가능
---
## 4. 개발 환경 및 배포 환경
### 4-1. 개발 환경
| 항목 | 내용 |
|---|---|
| 하드웨어 | 맥미니 |
| IDE | IntelliJ IDEA |
| 실행 방법 | IntelliJ 터미널에서 `pnpm dev` |
| 포트 | 3001 (기존 mouse-typing과 충돌 방지) |
| DB (개발) | 맥미니 로컬 MariaDB (`localhost:3306`) |
| 환경변수 | `.env.local` |
### 4-2. 배포(서비스) 환경
| 항목 | 내용 |
|---|---|
| 플랫폼 | Synology NAS DS920+ (RAM 20GB) — Docker |
| 실행 방법 | Synology Container Manager (`docker compose up`) |
| DB (서비스) | AWS EC2 MariaDB (chocomae 서비스와 동일 인스턴스) |
| DB 접근 | AWS 보안그룹에서 NAS 외부 IP만 3306 포트 허용 |
| DB 계정 | choco-admin 전용 최소 권한 계정으로 분리 |
| 환경변수 | `.env.production` 또는 Container Manager 환경변수 설정 |
### 4-3. Dockerfile / docker-compose (배포용)
```dockerfile
# Dockerfile
FROM node:22-alpine
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
CMD ["pnpm", "start"]
```
```yaml
# docker-compose.yml
services:
choco-admin:
build: .
ports:
- "3001:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=mysql://USER:PASS@AWS_EC2_HOST:3306/chocomae
- NEXTAUTH_SECRET=...
- NEXTAUTH_URL=http://NAS_IP:3001
restart: unless-stopped
```
---
## 5. 데이터베이스 주요 테이블
스키마: [`docs/db-reference/schema.sql`](./db-reference/schema.sql)
| 테이블 | 설명 | 주요 컬럼 |
|---|---|---|
| `maestro` | 마에스트로(강사) 계정 | `MaestroID`, `Name`, `Email`, `AccountType`, `ActivateStatus`, `AvailableActivateDateTime`, `PlayerCount` |
| `maestro_extension` | 연장 신청 | `MaestroExtensionID`, `MaestroID`, `AccountType`, `RequestedDateTime`, `Status` |
| `maestro_upgrade` | 업그레이드 신청 | `MaestroUpgradeID`, `MaestroID`, `RegisteredAccountType`, `RequestedAccountType`, `RequestedDateTime`, `Status` |
| `maestro_log` | 처리 이력 로그 | `MaestroLogID`, `MaestroID`, `Type`, `LogDateTime`, `Remark` |
| `admin` | 관리자 계정 | `AdminID`, `Name`, `Password`, `Email`, `AccountType`, `ActivateStatus` |
| `player` | 학생 계정 | `PlayerID`, `MaestroID`, `Name`, `EnterCode` |
### 상태값 코드표
**`maestro.AccountType`**
| 값 | 의미 |
|---|---|
| 1 | 20명 (1만원) |
| 2 | 50명 (2만원) |
| 3 | 100명 (3만원) |
| 4 | 500명 (4만원) |
| 5 | 1,000명 (5만원) |
**`maestro.ActivateStatus`**
| 값 | 의미 |
|---|---|
| 0 | 미승인 |
| 1 | 체험 |
| 2 | 활성화 |
| 100 | 등록취소 |
**`maestro_extension.Status` / `maestro_upgrade.Status`**
| 값 | 의미 |
|---|---|
| 1 | 요청 |
| 2 | 취소/거절 |
| 3 | 적용 완료 |
---
## 6. 애플리케이션 구조
```
choco-admin/
├── app/
│ ├── (auth)/
│ │ └── login/
│ │ └── page.tsx # 관리자 로그인
│ ├── (admin)/
│ │ ├── layout.tsx # 공통 사이드바 + 헤더
│ │ ├── maestros/
│ │ │ ├── page.tsx # 마에스트로 전체 목록
│ │ │ └── [maestroId]/
│ │ │ └── page.tsx # 마에스트로 상세 정보
│ │ ├── extension-requests/
│ │ │ └── page.tsx # 연장 신청 목록 + 승인/취소
│ │ └── upgrade-requests/
│ │ └── page.tsx # 업그레이드 신청 목록 + 승인/거절
│ └── api/
│ ├── auth/[...nextauth]/
│ ├── maestros/
│ │ ├── route.ts # GET: 목록
│ │ └── [id]/route.ts # GET: 상세, PATCH: 상태 변경
│ ├── extension-requests/
│ │ └── [id]/route.ts # PATCH: 승인/취소
│ └── upgrade-requests/
│ └── [id]/route.ts # PATCH: 승인/거절
├── components/
│ ├── ui/ # shadcn/ui 컴포넌트
│ ├── data-table/ # TanStack Table 공통 컴포넌트
│ ├── layout/ # 사이드바, 헤더
│ ├── forms/
│ └── dialogs/ # 승인/취소 확인 다이얼로그
├── lib/
│ ├── db.ts # Prisma Client 싱글톤
│ ├── auth.ts # NextAuth 설정
│ ├── validators/ # Zod 스키마
│ └── constants.ts # 상태값 상수
├── types/
│ └── index.ts
├── prisma/
│ └── schema.prisma # DB introspection 결과
├── docs/
│ └── db-reference/
│ └── schema.sql
├── .env.local # gitignore
├── .env.production # gitignore
├── Dockerfile
└── docker-compose.yml
```
---
## 7. 주요 화면 기능 명세
### 7-1. 공통 레이아웃
- 로그인 화면 (미인증 시 `/login` 리다이렉트)
- 좌측 사이드바 네비게이션
- 공통 확인 다이얼로그 (승인/취소/거절 전 확인)
- 처리 성공/실패 토스트 알림
### 7-2. 마에스트로 전체 목록 (`/maestros`)
- 전체 목록 조회 (서버 사이드 페이지네이션)
- 검색: 이름, 이메일, MaestroID
- 필터: ActivateStatus (미승인 / 체험 / 활성화 / 등록취소)
- 필터: AccountType (20명 / 50명 / 100명 / 500명 / 1,000명)
- 정렬: 가입일, 사용 가능일
- 컬럼: ID / 이름 / 이메일 / 계정 유형 / 상태 / 학생 수 / 사용 가능일 / 상세보기
### 7-3. 마에스트로 상세 정보 (`/maestros/[maestroId]`)
- 기본 계정 정보 (이름, 이메일, 계정 유형, 상태, 만료일, 약관 동의일)
- 플레이어(학생) 목록 요약
- 연장 신청 이력
- 업그레이드 신청 이력
- 관리자 처리 로그 (`maestro_log`)
### 7-4. 연장 신청 목록 (`/extension-requests`)
- 연장 신청 목록 조회 (기본 필터: Status=1 요청 건)
- 필터: 상태 (요청 / 취소 / 적용)
- 컬럼: 신청ID / 마에스트로명 / 계정유형 / 신청일시 / 상태 / 승인 / 취소
**승인 처리 순서:**
1. `maestro_extension.Status = 1` 인지 확인 (중복 처리 방지)
2. 트랜잭션 시작
3. `maestro_extension.Status → 3`
4. `maestro.AvailableActivateDateTime` 연장, `maestro.ActivateStatus → 2` (기존 서비스 로직 확인 후 동일 적용)
5. `maestro_log` 기록
6. 트랜잭션 커밋
**취소 처리 순서:**
1. `maestro_extension.Status = 1` 인지 확인
2. 트랜잭션 시작
3. `maestro_extension.Status → 2`
4. `maestro_log` 기록
5. 트랜잭션 커밋
### 7-5. 업그레이드 신청 목록 (`/upgrade-requests`)
- 업그레이드 신청 목록 조회 (기본 필터: Status=1 요청 건)
- 필터: 상태 (요청 / 취소 / 적용)
- 컬럼: 신청ID / 마에스트로명 / 현재등급 / 요청등급 / 신청일시 / 상태 / 승인 / 거절
**승인 처리 순서:**
1. `maestro_upgrade.Status = 1` 인지 확인 (중복 처리 방지)
2. 트랜잭션 시작
3. `maestro_upgrade.Status → 3`
4. `maestro.AccountType → RequestedAccountType` 변경 (PlayerCount 갱신 정책은 기존 서비스 확인 후 동일 적용)
5. `maestro_log` 기록
6. 트랜잭션 커밋
**거절 처리 순서:**
1. `maestro_upgrade.Status = 1` 인지 확인
2. 트랜잭션 시작
3. `maestro_upgrade.Status → 2`
4. `maestro_log` 기록
5. 트랜잭션 커밋
---
## 8. 보안 고려사항
1. 관리자 로그인은 NextAuth 서버 세션 기반으로 구현한다.
2. 기존 `admin.Password` 저장 방식(평문/해시/자체 암호화)을 먼저 확인한 뒤 동일 방식 또는 해시 업그레이드를 결정한다.
3. 승인·취소·거절 액션은 Next.js Server Action 또는 API Route에서 CSRF 보호를 적용한다.
4. DB 연결 정보는 `.env` 파일로 관리하며 Git에 커밋하지 않는다.
5. 배포 DB 계정은 choco-admin 전용 계정으로 분리하고 최소 권한(SELECT + 필요한 테이블만 UPDATE)을 적용한다.
6. AWS EC2 보안그룹에서 MariaDB 포트(3306)를 NAS 외부 IP로만 허용한다.
7. 관리자 전용 서비스이므로 외부에 직접 노출하지 않고 VPN 또는 NAS 내부 포트로 접근하는 방식을 권장한다.
---
## 9. 개발 단계 계획
### Phase 0. 기존 관리자 코드 조사
> 기존 서비스 비즈니스 규칙을 파악해 재현 실수를 방지한다.
- [x] `mouse-typing` 관리자 라우트와 처리 로직 분석 (`src/web/admin/`, `src/web/server/admin/`)
- [x] 연장 승인 시 `AvailableActivateDateTime` 계산 방식 확인 (몇 개월/년 연장인지)
- [x] 업그레이드 승인 시 `PlayerCount` 갱신 여부 확인
- [x] `admin.Password` 검증 방식 확인 (평문 / MD5 / bcrypt 등)
- [x] `maestro_log` 기록 형식(`Type`, `Remark`) 확인
- [x] 조사 결과를 `docs/business-rules.md`에 문서화
### Phase 1. 프로젝트 초기화
- [x] `pnpm create next-app` (Next.js 16, TypeScript, App Router, Tailwind CSS)
- [x] shadcn/ui 초기화 (`pnpm dlx shadcn@latest init`)
- [x] TanStack Table 설치 (`pnpm add @tanstack/react-table`)
- [x] Prisma 설치 및 MariaDB introspection (`pnpm add prisma`, `prisma db pull`)
- [x] Zod 설치 (`pnpm add zod`)
- [x] NextAuth.js v5 설치
- [x] ESLint / Prettier 설정
- [x] `.env.local` 환경변수 구조 정의
- [x] IntelliJ에서 `pnpm dev` 실행 확인
### Phase 2. DB 연결 및 타입 구성
- [ ] Prisma Client 싱글톤 (`lib/db.ts`)
- [ ] 상태값 상수 정의 (`lib/constants.ts`)
- [ ] 공통 유틸 작성 (날짜 포맷, AccountType 레이블 변환 등)
- [ ] 개발 DB에서 `maestro`, `maestro_extension`, `maestro_upgrade` 조회 확인
### Phase 3. 인증 및 공통 레이아웃
- [ ] 로그인 페이지 (`/login`)
- [ ] NextAuth Credentials Provider — `admin` 테이블 기반 검증
- [ ] 인증 미들웨어 (`middleware.ts`) — 비로그인 시 `/login` 리다이렉트
- [ ] 공통 레이아웃 (`(admin)/layout.tsx`) — 사이드바, 헤더
- [ ] 로그아웃 기능
### Phase 4. 마에스트로 목록 및 상세
- [ ] 마에스트로 목록 API (`GET /api/maestros`) — 검색·필터·페이지네이션 포함
- [ ] TanStack Table 기반 목록 화면
- [ ] 마에스트로 상세 API (`GET /api/maestros/[id]`)
- [ ] 상세 화면 — 연장/업그레이드 이력, 학생 목록 요약, 처리 로그
### Phase 5. 연장 신청 관리
- [ ] 연장 신청 목록 API + 화면
- [ ] 승인/취소 확인 다이얼로그 (shadcn/ui AlertDialog)
- [ ] 승인 API (`PATCH /api/extension-requests/[id]`) — 트랜잭션 + maestro_log
- [ ] 취소 API — 트랜잭션 + maestro_log
### Phase 6. 업그레이드 신청 관리
- [ ] 업그레이드 신청 목록 API + 화면
- [ ] 승인/거절 확인 다이얼로그
- [ ] 승인 API (`PATCH /api/upgrade-requests/[id]`) — 트랜잭션 + maestro_log
- [ ] 거절 API — 트랜잭션 + maestro_log
### Phase 7. 배포 환경 구성
- [ ] Dockerfile (프로덕션용 pnpm 빌드)
- [ ] `docker-compose.yml` (Synology NAS Container Manager용)
- [ ] AWS EC2 보안그룹 인바운드 설정 (NAS IP → 3306 허용)
- [ ] Synology NAS에서 컨테이너 실행 테스트
- [ ] 운영 DB read-only 리허설 후 변경 기능 활성화
### Phase 8. 검증 및 안정화
- [ ] 승인/취소/거절 트랜잭션 시나리오 테스트
- [ ] 중복 처리 방지 (이미 처리된 건 재처리 시 에러 확인)
- [ ] 비인증 접근 차단 확인
- [ ] Docker 재시작 후 정상 동작 확인
---
## 10. MVP 범위
**1차 MVP (위 Phase 0~7):**
- 관리자 로그인
- 마에스트로 전체 목록 및 상세 정보 보기
- 연장 신청 목록 / 승인 / 취소
- 업그레이드 신청 목록 / 승인 / 거절
- 관리자 액션 로그 기록
- Docker 기반 NAS 배포
**MVP 이후 확장 후보:**
- 마에스트로 상태 직접 변경
- 학생 목록 상세 관리
- 데이터 내보내기 (CSV)
- 처리 통계 대시보드
- 감사 로그 전용 화면
- 관리자 계정 관리
---
## 11. 구현 전 확인이 필요한 사항
기존 `mouse-typing` 서비스 코드(`src/web/server/admin/`)에서 반드시 확인해야 할 비즈니스 규칙들이다.
1. 연장 승인 시 `AvailableActivateDateTime`을 정확히 얼마나 연장하는가? (1년? 계정 유형별 다름?)
: 계정 유형별로 차이 없고, 1년 연장
2. 업그레이드 승인 시 `PlayerCount`를 즉시 변경하는가, 아니면 `AccountType`만 변경하는가?
: AccountType만 변경
3. `admin.Password`는 어떤 방식으로 저장/검증되는가? (평문, MD5, bcrypt 등)
: MariaDB의 Password() 함수를 이용해서 저장/검증됨
4. `maestro_log``Type` 컬럼에 어떤 값들이 사용되는가? (`Remark` 형식 포함)
: 다음과 같은 값들이 사용됨
* add_maestro
* update_maestro
* add_maestro
* register_maestro
* update_maestro_name
* extension_maestro
* upgrade_maestro
* update_maestro_email
5. 운영 MariaDB가 외부 IP 접속을 허용할 수 있는 네트워크 구조인가?
: 외부 IP 접속을 허용하고 있다
---
*작성일: 2026-05-26*