# chocoadmin 프로젝트 계획서 ## 1. 프로젝트 개요 | 항목 | 내용 | |---|---| | 서비스명 | chocoadmin | | 목적 | chocomae 웹 서비스(mouse-typing)의 관리자 기능을 독립 웹 서비스로 분리 | | 기존 서비스 경로 | `/Users/jisangs/work/jinaju/git/web/mouse-typing` | | 신규 서비스 경로 | `/Users/jisangs/work/jinaju/git/web/chocoadmin` | | 기존 운영 환경 | 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` | | 포트 | 3000 | | 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 계정 | chocoadmin 전용 최소 권한 계정으로 분리 | | 환경변수 | `.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: chocoadmin: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=mysql://USER:PASS@AWS_EC2_HOST:3306/chocomae - NEXTAUTH_SECRET=... - NEXTAUTH_URL=http://NAS_IP:3000 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. 애플리케이션 구조 ``` chocoadmin/ ├── 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 계정은 chocoadmin 전용 계정으로 분리하고 최소 권한(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 연결 및 타입 구성 - [x] Prisma Client 싱글톤 (`lib/db.ts`) - [x] 상태값 상수 정의 (`lib/constants.ts`) - [x] 공통 유틸 작성 (날짜 포맷, AccountType 레이블 변환 등) - [x] 개발 DB에서 `maestro`, `maestro_extension`, `maestro_upgrade` 조회 확인 ### Phase 3. 인증 및 공통 레이아웃 - [x] 로그인 페이지 (`/login`) - [x] NextAuth Credentials Provider — `admin` 테이블 기반 검증 - [x] 인증 프록시 (`proxy.ts`) — 비로그인 시 `/login` 리다이렉트 - [x] 공통 레이아웃 (`(admin)/layout.tsx`) — 사이드바, 헤더 - [x] 로그아웃 기능 ### Phase 4. 마에스트로 목록 및 상세 - [x] 마에스트로 목록 API (`GET /api/maestros`) — 검색·필터·페이지네이션 포함 - [x] TanStack Table 기반 목록 화면 - [x] 마에스트로 상세 API (`GET /api/maestros/[id]`) - [x] 상세 화면 — 연장/업그레이드 이력, 학생 목록 요약, 처리 로그 ### Phase 5. 연장 신청 관리 - [x] 연장 신청 목록 API + 화면 - [x] 승인/취소 확인 다이얼로그 - [x] 승인 API (`PATCH /api/extension-requests/[id]`) — 트랜잭션 + maestro_log - [x] 취소 API — 트랜잭션 + maestro_log ### Phase 6. 업그레이드 신청 관리 - [x] 업그레이드 신청 목록 API + 화면 - [x] 승인/거절 확인 다이얼로그 - [x] 승인 API (`PATCH /api/upgrade-requests/[id]`) — 트랜잭션 + maestro_log - [x] 거절 API — 트랜잭션 + maestro_log ### Phase 7. 배포 환경 구성 - [x] Dockerfile (프로덕션용 pnpm 빌드) - [x] `docker-compose.yml` (Synology NAS Container Manager용) - [ ] AWS EC2 보안그룹 인바운드 설정 (NAS IP → 3306 허용) — `docs/deployment.md`에 절차 문서화 - [ ] Synology NAS에서 컨테이너 실행 테스트 — `docs/deployment.md`에 절차 문서화 - [ ] 운영 DB read-only 리허설 후 변경 기능 활성화 — `docs/deployment.md`에 절차 문서화 ### 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*