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

33 KiB

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 테이블 활용)
이메일 발송 Nodemailer (기존 mouse-typing SMTP 설정을 환경변수로 이관)
환경변수 관리 .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
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"]
# 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

테이블 설명 주요 컬럼
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            # 공통 사이드바 + 헤더
│   │   ├── MobileNav.tsx         # 모바일 슬라이드인 사이드바 (햄버거·오버레이·X 버튼)
│   │   ├── nav-config.ts         # 내비게이션 항목 공유 설정
│   │   ├── maestros/
│   │   │   ├── page.tsx          # 마에스트로 전체 목록
│   │   │   └── [maestroId]/
│   │   │       ├── page.tsx                      # 마에스트로 상세 정보 (server)
│   │   │       ├── MaestroEditForm.tsx            # 기본 정보 수정 폼 (client)
│   │   │       ├── StudentsSection.tsx            # 학생 목록 + 검색/페이지네이션 (client)
│   │   │       ├── ExtensionRequestsSection.tsx   # 연장 신청 이력 + [연장] 버튼 (client)
│   │   │       └── UpgradeRequestsSection.tsx     # 업그레이드 신청 이력 + 신청 UI (client)
│   │   ├── extension-requests/
│   │   │   └── page.tsx          # 연장 신청 목록 + 승인/취소
│   │   └── upgrade-requests/
│   │       └── page.tsx          # 업그레이드 신청 목록 + 승인/거절
│   └── api/
│       ├── auth/[...nextauth]/
│       ├── maestros/
│       │   ├── route.ts          # GET: 목록
│       │   └── [id]/
│       │       ├── route.ts                      # GET: 상세, PATCH: 정보 수정
│       │       ├── students/route.ts             # GET: 학생 목록
│       │       ├── extension-requests/route.ts   # POST: 연장 신청 등록 (관리자 직접)
│       │       ├── upgrade-requests/route.ts     # POST: 업그레이드 신청 등록 (관리자 직접)
│       │       └── reset-password/route.ts       # POST: 비밀번호 123456 초기화
│       ├── 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 설정
│   ├── mail.ts                   # chocomae SMTP 메일 발송 모듈
│   ├── 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.ActivateStatus)
    • 계정유형: 콤보박스 (maestro.AccountType)
    • 사용 가능일 (maestro.AvailableActivateDateTime)
    • 입장코드 수정: 콤보박스 기반 입력/선택 UI
  • 정보 수정 및 저장 성공 시 maestro_log에 처리 이력 기록
  • 플레이어(학생) 전체 목록
    • 서버 사이드 페이지네이션
    • 이름 검색
  • 연장 신청 이력 + [연장] 버튼
    • 유료 요금제 계정에서만 버튼 활성화 (체험 계정 비활성)
    • 클릭 시 현재 AccountType으로 maestro_extension INSERT (서버에서 DB 조회)
    • 등록 후 이력 목록 즉시 갱신 (router.refresh())
  • 업그레이드 신청 이력 + 요금제 콤보박스 + [업그레이드] 버튼
    • 콤보박스: 현재 요금제 이하 항목 비활성화, 상위 요금제만 선택 가능
    • 클릭 시 선택한 requestedAccountType으로 maestro_upgrade INSERT (트랜잭션 내 서버 조회)
    • 서버에서 requestedAccountType > maestro.AccountType 검증 (다운그레이드 차단)
    • 등록 후 이력 목록 즉시 갱신 (router.refresh())
  • 관리자 처리 로그 (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. 트랜잭션 커밋
  7. 커밋 후 연장 완료 이메일 자동 발송

연장 완료 이메일:

  • 기존 호출 위치: /Users/jisangs/work/jinaju/git/web/mouse-typing/src/web/server/admin/extension_maestro.php:29
  • 제목: [초코마에] {마에스트로명} - 마에스트로 계정 유효기간 연장 완료
  • 내용: 유효기간 +1년 연장 완료, 새 유효기간 안내

취소 처리 순서:

  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. 트랜잭션 커밋
  7. 커밋 후 업그레이드 완료 이메일 자동 발송

업그레이드 완료 이메일:

  • 기존 호출 위치: /Users/jisangs/work/jinaju/git/web/mouse-typing/src/web/server/admin/upgrade_maestro.php:30
  • 제목: [초코마에] {마에스트로명} - 마에스트로 계정의 요금제 업그레이드 완료 알림
  • 내용: 기존 요금제에서 상위 요금제로 변경 완료, 유효기간 오늘부터 1년

거절 처리 순서:

  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 내부 포트로 접근하는 방식을 권장한다.
  8. SMTP/API 인증 정보, 발송자 주소, BCC 주소는 환경변수 또는 배포 시크릿으로 관리하고 코드에 하드코딩하지 않는다.

9. 개발 단계 계획

Phase 0. 기존 관리자 코드 조사

기존 서비스 비즈니스 규칙을 파악해 재현 실수를 방지한다.

  • mouse-typing 관리자 라우트와 처리 로직 분석 (src/web/admin/, src/web/server/admin/)
  • 연장 승인 시 AvailableActivateDateTime 계산 방식 확인 (몇 개월/년 연장인지)
  • 업그레이드 승인 시 PlayerCount 갱신 여부 확인
  • admin.Password 검증 방식 확인 (평문 / MD5 / bcrypt 등)
  • maestro_log 기록 형식(Type, Remark) 확인
  • 조사 결과를 docs/business-rules.md에 문서화

Phase 1. 프로젝트 초기화

  • pnpm create next-app (Next.js 16, TypeScript, App Router, Tailwind CSS)
  • shadcn/ui 초기화 (pnpm dlx shadcn@latest init)
  • TanStack Table 설치 (pnpm add @tanstack/react-table)
  • Prisma 설치 및 MariaDB introspection (pnpm add prisma, prisma db pull)
  • Zod 설치 (pnpm add zod)
  • NextAuth.js v5 설치
  • ESLint / Prettier 설정
  • .env.local 환경변수 구조 정의
  • 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 테이블 기반 검증
  • 인증 프록시 (proxy.ts) — 비로그인 시 /login 리다이렉트
  • 공통 레이아웃 ((admin)/layout.tsx) — 사이드바, 헤더
  • 로그아웃 기능

Phase 4. 마에스트로 목록 및 상세

  • 마에스트로 목록 API (GET /api/maestros) — 검색·필터·페이지네이션 포함
  • TanStack Table 기반 목록 화면
  • 마에스트로 상세 API (GET /api/maestros/[id])
  • 상세 화면 — 연장/업그레이드 이력, 학생 목록 요약, 처리 로그

Phase 5. 연장 신청 관리

  • 연장 신청 목록 API + 화면
  • 승인/취소 확인 다이얼로그
  • 승인 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에서 컨테이너 실행 확인 (production + stage 동시 운영 중)
  • 운영 DB 연결 및 변경 기능 확인

Phase 8. 검증 및 안정화

  • 승인/취소/거절 트랜잭션 시나리오 테스트 절차 문서화 (docs/phase/phase8.md)
  • 중복 처리 방지 코드 확인 (이미 처리된 건 재처리 시 409 응답)
  • 비인증 접근 차단 코드 확인 (proxy.ts, API route auth() 검사)
  • Docker 재시작 후 정상 동작 확인 절차 문서화 (docs/phase/phase8.md)

Phase 9. 실행 로그 및 DB 쿼리 로그

local/stage에서는 개발과 디버깅에 충분한 상세 로그를 남기고, production에서는 운영에 필요한 수준으로 로그 양과 민감정보 노출을 제한한다.

  • 로그 정책 정의 문서 작성 (docs/phase/phase9.md)
    • 환경 구분: local, stage, production
    • 로그 레벨 구분: debug, info, warn, error
    • 로그 출력 형식: 사람이 읽기 쉬운 콘솔 출력 + 필요 시 JSON line 확장 가능 구조
    • 민감정보 마스킹 대상 정의: 비밀번호, 인증 토큰, 쿠키, DB 접속 문자열, secret, 개인정보성 필드
  • 서버 실행 로그 유틸 구현
    • 공통 logger 모듈 추가 (lib/logger.ts)
    • request 단위 correlation id 생성 및 로그에 포함 (보류 — Edge Middleware 제약으로 헤더 주입 방식 필요, docs/phase/phase9.md 참고)
    • API route 시작/종료, 처리 시간, status code, action 결과 기록 (lib/api-handler.ts withApiHandler 래퍼로 공통 처리)
    • 인증 실패, 권한 없음, 유효성 검증 실패, 비즈니스 예외를 구분해 기록 (ApiError + withApiHandler)
  • DB query 로그 구현
    • Prisma query event 기반 query 로깅 적용 (lib/db.ts $on("query", ...))
    • local/stage: query문, params, duration, 결과를 sql-formatter로 pretty 출력 ($on + $extends.$allOperations)
    • production: LOG_LEVEL=info 설정 시 debug 로그 전체 억제 — slow query만 warn으로 출력
    • production slow query threshold 설정 (DB_SLOW_QUERY_MS, 기본 1000ms)
    • 결과 로그 최대 길이 제한 (DB_LOG_RESULT_LIMIT) 및 초과 시 truncate 표시 (미구현)
  • 환경변수 추가
    • APP_ENV=local|stage|production
    • LOG_LEVEL=debug|info|warn|error
    • DB_QUERY_LOG=off|summary|full (미구현 — 현재는 LOG_LEVEL로 제어)
    • DB_SLOW_QUERY_MS
    • DB_LOG_RESULT_LIMIT (미구현)
  • 주요 API에 로그 적용
    • 로그인 성공/실패 로그 (auth.ts)
    • 마에스트로 목록/상세 조회 로그 (lib/maestros.ts)
    • 연장 신청 조회/승인/취소 로그 (lib/extension-requests.ts, app/api/extension-requests/[id]/route.ts)
    • 업그레이드 신청 조회/승인/거절 로그 (lib/upgrade-requests.ts, app/api/upgrade-requests/[id]/route.ts)
    • 중복 처리 방지로 409 반환 시 warning 로그 (withApiHandlerApiError 캐치 후 warn 출력)
  • Docker/NAS 로그 운영 절차 문서화 (docs/phase/phase9.md)
    • docker compose logs -f 확인 방법
    • stage와 production의 권장 .env 로그 설정 예시
    • Nginx Proxy Manager access/error log와 애플리케이션 로그를 같이 확인하는 절차
  • 검증
    • local에서 query문과 결과 전체가 출력되는지 확인
    • stage에서 query문과 결과 전체가 출력되는지 확인
    • production 설정에서 결과 전체가 출력되지 않고 summary/slow query 중심으로 출력되는지 확인
    • 로그에 비밀번호, token, cookie, secret, DB password가 노출되지 않는지 확인 (onQuery에서 PASSWORD( 포함 쿼리는 params 치환 생략)

Phase 10. 마에스트로 정보 수정 및 학생 목록 고도화

/maestros/[maestroId] 상세 화면에서 관리자 계정 정보 수정과 학생 목록 조회를 완성한다.

  • 기존 mouse-typing 마에스트로 정보 수정 로직 조사
    • 이름 변경 시 중복 검사 기준 확인 (maestro.Name, 자기 자신 제외)
    • 이메일 변경 처리와 로그 형식 확인
    • 입장코드 수정 대상 컬럼과 기존 값 생성/검증 규칙 확인
    • maestro_log.Type / Remark 형식을 기존 서비스와 맞춤
  • 마에스트로 정보 수정 API 구현 (PATCH /api/maestros/[id])
    • Zod 요청 스키마 정의: 이름, 이메일, 상태, 계정유형, 사용 가능일, 입장코드
    • 관리자 인증 및 권한 검증
    • 이름 중복 검사: 다른 계정이 사용 중이면 저장 실패 응답
    • 트랜잭션으로 maestro 정보 수정
    • 변경 전/후 값을 포함해 maestro_log 기록
    • 변경 없음, 유효하지 않은 상태값, 존재하지 않는 마에스트로에 대한 응답 정책 정리
  • 마에스트로 상세 화면 수정 폼 구현
    • 이름/이메일 입력
    • 상태 콤보박스
    • 계정유형 콤보박스
    • 사용 가능일 입력
    • 입장코드 수정 콤보박스
    • 저장 전 확인, 저장 중 비활성화, 성공/실패 토스트
  • 학생 목록 API 구현 (GET /api/maestros/[id]/students)
    • 전체 학생 목록 조회
    • 서버 사이드 페이지네이션
    • 이름 검색
    • 정렬 기준과 기본 page size 정의
  • 학생 목록 UI 구현
    • 이름 검색 입력
    • 페이지네이션 컨트롤
    • 빈 목록/검색 결과 없음 상태
  • 검증
    • 이름 중복 저장 실패 확인
    • 정보 수정 성공 시 DB 반영과 maestro_log 기록 확인
    • 학생 목록 페이지네이션/검색 동작 확인
    • 비인증 접근 차단 확인

Phase 11. 이메일 발송 환경 및 자동 발송

기존 chocomae 서비스의 관리자 승인 완료 메일을 chocoadmin에서도 동일하게 발송한다. 상세 계획: docs/phase/phase11.md

  • 기존 이메일 발송 설정 이관 준비
    • 기존 서비스 파일 확인: send_jinaju_mail.php — SMTP gmail:465, SMTPS, 발송자/BCC 주소 확인
    • 기존 서비스 파일 확인: mail_contents.php — 연장/업그레이드 완료 이메일 제목·본문 확인
    • 기존 서비스 파일 확인: mail_setting.php — From, BCC, sender_name 확인
    • .env.local에 메일 환경변수 추가 (SMTP 비밀번호는 배포 시크릿으로 분리)
  • nodemailer 패키지 설치 (pnpm add nodemailer && pnpm add -D @types/nodemailer)
  • 메일 발송 모듈 구현 (lib/mail.ts)
    • createTransporter() — 환경변수 기반 SMTP transport
    • sendMail() — 공통 발송 함수 (MAIL_SEND_ENABLED 체크, From/BCC/ReplyTo 구성, 성공·실패 로그)
    • sendExtensionDoneEmail() — 연장 완료 이메일 템플릿
    • sendUpgradeDoneEmail() — 업그레이드 완료 이메일 템플릿 (체험→유료 / 유료→유료 내부 분기)
  • lib/extension-requests.ts 수정
    • approveExtensionRequest 반환값에 maestroName, maestroEmail 추가 (이미 include: { maestro: true } 있음 — 추가 쿼리 불필요)
  • lib/upgrade-requests.ts 수정
    • getActionTargetinclude: { maestro: { select: { Name, Email } } } 추가
    • approveUpgradeRequest 반환값에 maestroName, maestroEmail, registeredActivateStatus, registeredAccountType, requestedAccountType 추가
  • 승인 API에 이메일 자동 발송 연결
    • app/api/extension-requests/[id]/route.ts — 승인 후 void sendExtensionDoneEmail(...) 호출
    • app/api/upgrade-requests/[id]/route.ts — 승인 후 void sendUpgradeDoneEmail(...) 호출
    • 메일 발송 실패 정책 적용: 실패 시 logger.error만 기록, 승인 결과(200)는 그대로 반환 (롤백 없음)
  • 검증
    • MAIL_SEND_ENABLED=false에서 승인 시 send skipped 로그 확인, 승인 결과 200 정상
    • MAIL_SEND_ENABLED=true에서 연장 승인 후 이메일 제목/본문/수신자/BCC 확인
    • 업그레이드 승인 후 체험→유료 케이스: 0원 : 무료 체험 기간 본문 확인
    • 업그레이드 승인 후 유료→유료 케이스: {prev_price}원 본문 확인
    • SMTP 오류 시 승인 API 200 응답 + error: send failed 로그 확인

Phase 12. 관리자 직접 연장/업그레이드 신청 등록

마에스트로 상세 화면에서 관리자가 직접 연장·업그레이드 신청을 DB에 등록할 수 있도록 한다. 메일 발송은 하지 않는다.

  • 연장 신청 등록 API 구현 (POST /api/maestros/[id]/extension-requests)
    • 서버에서 maestro.AccountType 직접 조회 — 클라이언트 값 신뢰 안 함
    • 유료 요금제 계정이 아닌 경우 400 반환 (체험 계정 차단)
    • maestro_extension INSERT (AccountType, RequestedDateTime, Status=1)
  • 업그레이드 신청 등록 API 구현 (POST /api/maestros/[id]/upgrade-requests)
    • 트랜잭션 내에서 maestro SELECT → maestro_upgrade INSERT (원자성 보장)
    • requestedAccountType > maestro.AccountType 검증 (다운그레이드 차단, 400 반환)
    • RegisteredActivateStatus, RegisteredAccountType 서버 DB 조회값으로 저장
  • 연장 신청 이력 클라이언트 컴포넌트 (ExtensionRequestsSection.tsx)
    • 섹션 우상단 [연장] 버튼
    • 체험 계정 (AccountTypePAID_ACCOUNT_TYPE_VALUES) 시 버튼 비활성화
    • 등록 성공 후 router.refresh()로 이력 테이블 즉시 갱신
  • 업그레이드 신청 이력 클라이언트 컴포넌트 (UpgradeRequestsSection.tsx)
    • 섹션 우상단 요금제 콤보박스 + [업그레이드] 버튼
    • 현재 요금제 이하 옵션 disabled, 상위 요금제만 선택 가능
    • 선택 후 등록 성공 시 콤보박스 초기화 + router.refresh()
  • PAID_ACCOUNT_TYPE_VALUES 공용 상수로 추출 (lib/constants.ts)
    • 기존 lib/maestros.ts의 로컬 정의 제거 및 import로 교체
    • 두 신규 API route의 로컬 정의 제거 및 import로 교체

Phase 13. 보안·정합성 수정

AGENTS.md "Implemented phases" Phase 13 항목 참조.

  • 모든 항목 완료 (P0-1 ~ P2-8)

Phase 14. 비밀번호 초기화 및 배포 스크립트

마에스트로 계정 비밀번호 초기화 기능을 추가한다. 완료 날짜: 2026-07-05

  • 비밀번호 초기화 API 구현 (POST /api/maestros/[id]/reset-password)
    • resetMaestroPassword(maestroID) 추가 (lib/maestros.ts)
    • $transaction 내에서 UPDATE maestro SET Password = PASSWORD('123456') 실행
    • reset_maestro_password 타입으로 maestro_log 기록
    • withApiHandler로 인증·에러 처리 공통화
  • 비밀번호 초기화 버튼 UI (MaestroEditForm.tsx)
    • [암호 초기화] 버튼을 [저장] 버튼 우측에 추가 (variant="outline", KeyRound 아이콘)
    • window.confirm으로 {마에스트로명} 계정의 비밀번호를 123456으로 초기화 하시겠습니까? 확인
    • 성공 시 기존 메시지 영역에 암호 초기화 완료 표시

Phase 15. Production/Stage 배포 환경 안정화

Production 첫 배포 과정에서 발견된 이슈들을 해결하고 NAS 배포 자동화 스크립트를 추가한다. 완료 날짜: 2026-07-05

  • Production 배포 경로 생성 및 git pull 방식 배포 설정
    • Production: /volume1/docker/service/jinaju/chocoadmin + docker-compose.yml
    • Stage: 동일 경로, -f docker-compose.stage.yml 로 구분
  • 포트 충돌 수정 (docker-compose.yml)
    • Gitea가 호스트 3000 포트를 선점 → ports: "3000:3000"expose: "3000" 으로 변경
  • Docker DNS alias 충돌 수정 (docker-compose.stage.yml)
    • services: chocoadmin:services: chocoadmin-stage: 으로 변경
    • 동일 서비스명은 proxy-network에서 같은 DNS alias로 등록됨
    • NPM의 set $server "chocoadmin" 이 두 컨테이너에 라운드로빈 → 트래픽이 production/stage 사이에 무작위 분산
  • NextAuth v5 커스텀 쿠키 이름으로 production/stage 쿠키 분리
    • auth.ts: cookies.sessionToken.name = "chocoadmin-${APP_ENV}.session-token" 추가
    • proxy.ts: getToken({ cookieName }) 추가 — auth.ts에만 적용하면 middleware가 세션 토큰을 찾지 못해 ERR_TOO_MANY_REDIRECTS 발생
    • __Secure- prefix 금지: Node.js 런타임은 Nginx에서 HTTP로 수신하므로 Secure 쿠키가 무시됨
  • 서버 액션 인라인 클로저 제거 — 로그아웃 액션을 app/(admin)/actions.ts 로 추출
    • 인라인 "use server" 클로저는 빌드마다 새 ID 부여 → 재배포 후 Failed to find Server Action
    • 코드베이스 전체 인라인 서버 액션 검토 및 동일 패턴 수정 완료
  • Production/Stage 동시 운영 정상 확인
  • 배포 자동화 스크립트 추가 (scripts/)
    • scripts/deploy-stage.sh — git pull → proxy-network 확인 → docker compose -f docker-compose.stage.yml up -d --build --remove-orphans → NPM reload
    • scripts/deploy-production.shyes 입력 확인 후 동일 5단계 실행 (docker-compose.yml 사용)
    • 각 스크립트는 NAS 해당 디렉토리에서 직접 실행

Phase 16. 반응형 웹 디자인

모바일 화면(360~430px)에서도 관리자 패널을 정상 사용할 수 있도록 사이드바 내비게이션, 표 overflow, 페이지네이션 정렬을 개선한다. 완료 날짜: 2026-07-07

  • 모바일 사이드바
    • app/(admin)/nav-config.ts 생성 — navItems 배열을 layout.tsxMobileNav.tsx가 공유
    • app/(admin)/MobileNav.tsx 생성 — 햄버거 버튼(md:hidden), 반투명 오버레이(탭하면 닫힘), 슬라이드인 패널(X 버튼), ESC 키 지원, pathname 변경 시 자동 닫힘, body 스크롤 잠금, CSS translate-x 트랜지션 애니메이션
    • app/(admin)/layout.tsx 수정 — 헤더에 <MobileNav /> 통합; 데스크탑 <aside>(hidden md:block)는 그대로 유지
  • 표 overflow 수정 (ExtensionRequestsSection.tsx, UpgradeRequestsSection.tsx)
    • 원인: 두 섹션은 서버 props로 데이터를 받아 SSR 초기 렌더에 min-w-[560px] 표가 포함됨 → sectionoverflow 미설정(visible 기본값)이어서 표가 섹션을 밀어냄 → overflow-x-auto가 확장된 너비를 그대로 가져가 스크롤 미발생. StudentsSection·LogsSection은 클라이언트 fetch 방식이라 SSR 초기 렌더에 표가 없어 동일 패턴으로도 정상 동작.
    • <section>overflow-hidden 추가 — BFC 확립으로 섹션 너비를 뷰포트 기준으로 고정
    • 섹션 헤더를 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between으로 변경 — 모바일에서 세로 스택, 액션 버튼 접근성 확보
  • 페이지네이션 오른쪽 정렬 — <nav>self-end sm:self-auto 추가 (모바일 flex-col에서 오른쪽 정렬, 데스크탑 flex-row에서 기존 동작 유지)
    • app/(admin)/extension-requests/page.tsx
    • app/(admin)/upgrade-requests/page.tsx
    • app/(admin)/maestros/page.tsx
    • app/(admin)/maestros/[maestroId]/StudentsSection.tsx
    • app/(admin)/maestros/[maestroId]/LogsSection.tsx

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_logType 컬럼에 어떤 값들이 사용되는가? (Remark 형식 포함) : 다음과 같은 값들이 사용됨
  • add_maestro
  • update_maestro
  • add_maestro
  • register_maestro
  • update_maestro_name
  • extension_maestro
  • upgrade_maestro
  • update_maestro_email
  1. 운영 MariaDB가 외부 IP 접속을 허용할 수 있는 네트워크 구조인가? : 외부 IP 접속을 허용하고 있다

작성일: 2026-05-26