Files
jaengseung-made/docs/superpowers/plans/2026-07-02-phase0-cleanup.md
2026-07-02 14:00:40 +09:00

19 KiB

Phase 0 정리·삭제 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 새 운영 비전(외주 메인 + 사주·타로·음악)에 없는 기능(eBay 세트·packages/subscription·PortOne)과 도달 불가능한 죽은 코드를 제거하고, DB 마이그레이션과 문서를 정합화한다.

Architecture: 삭제 전용 리팩토링. 6개 삭제 그룹을 독립 커밋으로 진행하고, 각 커밋마다 npm test + npm run build로 회귀를 차단한다. IA(네비·홈)와 next.config.ts 리다이렉트는 건드리지 않는다.

Tech Stack: Next.js 16 (App Router), TypeScript, Supabase, vitest

Spec: docs/superpowers/specs/2026-07-02-saas-operation-refactor-phase0-design.md

Global Constraints

  • next.config.ts의 redirects()는 한 줄도 수정 금지 (외부 URL 호환)
  • app/work/website/samples/** 8종, app/work/layout.tsx, app/work/website/layout.tsx삭제 금지 (Phase 1 자산 + 경로 세그먼트 유지)
  • gyeol 세트(/gyeol, /api/survey, admin/survey, survey_responses 테이블)는 삭제 금지 (CEO 의도적 보존)
  • app/api/projects/**, telegram 3종(webhook·connect·setup), lib/telegram.ts삭제 금지 (Phase 1~3 재활용)
  • 기존 마이그레이션 파일은 이력이므로 삭제·수정 금지, 신규 파일만 추가
  • 각 Task 종료 시 npm test 전체 통과 + npm run build 성공 후 커밋
  • 커밋 메시지 끝에 다음 트레일러 포함: Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Task 1: eBay 세트 삭제

Files:

  • Delete: app/api/questionnaire/ (submit/route.ts 포함 디렉토리)
  • Delete: app/admin/questionnaire/page.tsx (디렉토리째)
  • Delete: app/api/admin/questionnaire/ (route.ts + [id]/route.ts 디렉토리)
  • Delete: app/admin/documents/page.tsx (디렉토리째)
  • Delete: app/api/admin/documents/ ([filename]/route.ts 디렉토리)
  • Delete: lib/ebay-tools/ (crawler.ts·pricing.ts·ai-analyzer.ts·types.ts)
  • Delete: CONTENT/ebay-tool-questionnaire.html, CONTENT/ebay-tool-proposal.html, CONTENT/ARCHITECTURE_EBAY_PARTS_TOOL.md
  • Modify: app/admin/components/AdminSidebar.tsx (NAV_ITEMS 2항목 제거)
  • Modify: package.json (cheerio 제거 — 유일 소비처가 lib/ebay-tools/crawler.ts:1)

Interfaces:

  • Consumes: 없음 (독립 삭제)

  • Produces: 없음. 이후 Task는 이 파일들이 없다고 가정

  • Step 1: 파일 삭제

git rm -r app/api/questionnaire app/admin/questionnaire app/api/admin/questionnaire \
  app/admin/documents app/api/admin/documents lib/ebay-tools \
  CONTENT/ebay-tool-questionnaire.html CONTENT/ebay-tool-proposal.html CONTENT/ARCHITECTURE_EBAY_PARTS_TOOL.md
  • Step 2: AdminSidebar에서 메뉴 2개 제거

app/admin/components/AdminSidebar.tsx의 NAV_ITEMS 배열에서 다음 두 객체를 통째로 제거 (href 기준으로 식별, 각 객체는 { href, label, icon } 형태로 svg 포함 약 20줄):

  • href: '/admin/documents' (label '프로젝트 문서', 약 79~100행)

  • href: '/admin/questionnaire' (label '질문지 응답', 약 101~122행)

  • Step 3: cheerio 의존성 제거

npm uninstall cheerio
  • Step 4: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -iE 'ebay|questionnaire' app lib
# 기대: 0건
npm test        # 기대: 전체 PASS
npm run build   # 기대: 빌드 성공
  • Step 5: 커밋
git add -A
git commit -m "chore(phase0): eBay 세트 제거 — 문진·문서 admin/API/lib/CONTENT + cheerio"

Task 2: packages + subscription 삭제

Files:

  • Delete: app/packages/ (page.tsx + layout.tsx)
  • Delete: lib/saas-catalog.ts
  • Delete: app/api/subscription/ (route.ts + [id]/route.ts)
  • Delete: app/api/cron/subscription-expiry/ (route.ts — cron 디렉토리에 다른 항목 없으면 app/api/cron/째)
  • Delete: vercel.json (내용이 subscription cron 하나뿐)
  • Modify: lib/service-visibility.ts:6 (HideableService 타입에서 'packages' 제거)
  • Modify: app/api/admin/services/route.ts:57 (DEFAULT_SERVICES에서 packages 행 제거)
  • Modify: app/api/admin/stats/route.ts:18-31,52 (subscriptions 집계 제거)
  • Modify: app/admin/dashboard/page.tsx:10,160-171 (activeSubscribers 제거)
  • Modify: app/api/admin/members/route.ts:30-37 (subsRes/activeSub 제거)
  • Modify: app/admin/members/page.tsx:12,15,94-101,127-131,154-158 (activeSub UI 제거)
  • Modify: app/work/saju/result/page.tsx:113-136 (subscriptions 쿼리 제거, orders 단일화)

Interfaces:

  • Consumes: 없음

  • Produces: /api/admin/stats 응답에서 activeSubscribers 필드 소멸, /api/admin/members 응답에서 activeSub 필드 소멸. 이 두 API의 프론트 소비처 수정까지 이 Task에 포함

  • Step 1: 파일 삭제

git rm -r app/packages lib/saas-catalog.ts app/api/subscription app/api/cron vercel.json

(사전 확인: ls app/api/cron 결과가 subscription-expiry뿐이면 cron째 삭제, 아니면 subscription-expiry만)

  • Step 2: HideableService 타입에서 packages 제거

lib/service-visibility.ts:

// 변경 전
export type HideableService = 'saju' | 'music' | 'gyeol' | 'packages' | 'lotto';
// 변경 후
export type HideableService = 'saju' | 'music' | 'gyeol' | 'lotto';
  • Step 3: DEFAULT_SERVICES에서 packages 행 제거

app/api/admin/services/route.ts에서 다음 한 줄 삭제:

  { id: 'packages', name: 'SaaS 제품 허브(구)', description: '구 /packages 페이지',             is_active: false, order_index: 104 },
  • Step 4: admin/stats에서 구독 집계 제거

app/api/admin/stats/route.ts:

// 변경 전 (18행, 24행, 31행, 52행)
const [profilesRes, ordersRes, paymentsRes, contactsRes, monthlyRes, subsRes] = await Promise.all([
  ...
  supabase.from('subscriptions').select('id', { count: 'exact', head: true }).eq('status', 'active'),
]);
const activeSubscribers = subsRes.count ?? 0;
return NextResponse.json({ totalMembers, totalOrders, totalRevenue, pendingContacts, activeSubscribers, monthlyChart });

// 변경 후
const [profilesRes, ordersRes, paymentsRes, contactsRes, monthlyRes] = await Promise.all([
  supabase.from('profiles').select('id', { count: 'exact', head: true }),
  supabase.from('orders').select('id', { count: 'exact', head: true }).eq('status', 'paid'),
  supabase.from('payments').select('amount').eq('status', 'paid'),
  supabase.from('contact_requests').select('id', { count: 'exact', head: true }).eq('status', 'pending'),
  supabase.from('payments').select('amount, created_at').eq('status', 'paid').order('created_at', { ascending: true }),
]);
// activeSubscribers 계산 라인 삭제
return NextResponse.json({ totalMembers, totalOrders, totalRevenue, pendingContacts, monthlyChart });
  • Step 5: admin/dashboard에서 활성 구독자 카드 제거

app/admin/dashboard/page.tsx:

  • 인터페이스에서 activeSubscribers: number; 필드 삭제 (10행)

  • label="활성 구독자"<StatCard ... /> 블록(약 160~171행, svg 포함) 통째로 삭제

  • Step 6: admin/members API에서 구독 조회 제거

app/api/admin/members/route.ts:

// 변경 전 (30~37행)
const [ordersRes, paymentsRes, subsRes] = await Promise.all([
  supabase.from('orders').select('id', { count: 'exact', head: true }).eq('user_id', p.id).eq('status', 'paid'),
  supabase.from('payments').select('amount').eq('user_id', p.id).eq('status', 'paid'),
  supabase.from('subscriptions').select('product_id, status, expires_at').eq('user_id', p.id).eq('status', 'active').order('created_at', { ascending: false }).limit(1),
]);
const totalPaid = (paymentsRes.data ?? []).reduce((s: number, x: { amount: number }) => s + x.amount, 0);
const activeSub = subsRes.data?.[0] ?? null;
return { ...p, orderCount: ordersRes.count ?? 0, totalPaid, activeSub };

// 변경 후
const [ordersRes, paymentsRes] = await Promise.all([
  supabase.from('orders').select('id', { count: 'exact', head: true }).eq('user_id', p.id).eq('status', 'paid'),
  supabase.from('payments').select('amount').eq('user_id', p.id).eq('status', 'paid'),
]);
const totalPaid = (paymentsRes.data ?? []).reduce((s: number, x: { amount: number }) => s + x.amount, 0);
return { ...p, orderCount: ordersRes.count ?? 0, totalPaid };
  • Step 7: admin/members 페이지에서 activeSub UI 제거

app/admin/members/page.tsx에서:

  • Member 인터페이스의 activeSub: { product_id: string; status: string; expires_at: string } | null; 필드 삭제 (12행)

  • PLAN_LABELS 상수 삭제 (15행~)

  • 테이블의 구독 셀 블록 삭제 (94~101행: {m.activeSub ? (...) : (<span ...>-</span>)} — 해당 <td>와 대응하는 <th> 헤더도 함께)

  • 모바일 카드의 구독 뱃지 블록 삭제 (127~131행: {m.activeSub && (<span ...>...)})

  • 구독 만료 문구 블록 삭제 (154~158행: {m.activeSub && (<p ...>구독 만료: ...</p>)})

  • Step 8: saju result에서 subscriptions 쿼리 제거 (orders 단일화)

app/work/saju/result/page.tsx의 113~136행을 다음으로 교체:

      // 로또 이용권 확인 — orders 테이블 (최근 31일 paid 주문)
      const thirtyOneDaysAgo = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000).toISOString();
      const { data: lottoOrder } = await supabase
        .from('orders')
        .select('id, created_at')
        .eq('user_id', user.id)
        .eq('status', 'paid')
        .in('product_id', ['lotto_gold', 'lotto_platinum', 'lotto_diamond', 'lotto_annual'])
        .gte('created_at', thirtyOneDaysAgo)
        .maybeSingle();
      hasLottoSubscription = !!lottoOrder;

(hasLottoSubscription 변수명·SajuFortuneSection prop은 유지 — 시맨틱은 Phase 2에서 재정의)

  • Step 9: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -E "saas-catalog|from\('subscriptions'\)|'packages'|activeSubscribers|activeSub\b" app lib
# 기대: 0건
npm test && npm run build
  • Step 10: 커밋
git add -A
git commit -m "chore(phase0): packages·subscription 제거 — 페이지/API/cron/vercel.json + 파급(stats·members·saju) 수정"

Task 3: PortOne 결제 잔재 삭제

Files:

  • Delete: app/components/PaymentButton.tsx
  • Delete: app/payment/ (test·fail·success 3페이지)
  • Delete: app/api/payment/ (confirm/route.ts)
  • Delete: lib/payment-channels.ts, lib/products.ts
  • Modify: app/work/saju/page.tsx:5 (미사용 import 삭제)
  • Modify: app/work/saju/result/SajuAISection.tsx:6,316-322 (PaymentButton → 안내 문구)
  • Modify: package.json (@portone/browser-sdk 제거)

Interfaces:

  • Consumes: 없음

  • Produces: PaymentButton 컴포넌트 소멸 — 이후 어떤 Task/Phase도 import 불가. 결제는 BankTransferModal(계좌이체) 단일 경로

  • Step 1: 파일 삭제

git rm -r app/components/PaymentButton.tsx app/payment app/api/payment lib/payment-channels.ts lib/products.ts
  • Step 2: saju 페이지의 미사용 import 삭제

app/work/saju/page.tsx 5행 삭제 (렌더 사용처 없음 확인됨):

import PaymentButton from '@/app/components/PaymentButton';
  • Step 3: SajuAISection의 결제 버튼을 안내 문구로 교체

app/work/saju/result/SajuAISection.tsx:

  • 6행 import 삭제: import PaymentButton from '@/app/components/PaymentButton';
  • 316~322행을 다음으로 교체:
          <p className="inline-flex items-center gap-2 bg-white/10 text-blue-100/80 font-semibold px-7 py-3 rounded-xl">
            AI 상세 해석은 서비스 개편 준비 중입니다
          </p>
          <p className="text-blue-200/40 text-xs mt-3">사주 서비스 개편(Phase 2)에서 무료 제공 예정</p>

(hasPaid 게이트 로직은 유지 — orders 테이블 기반이라 그대로 컴파일됨. 무료화 UX는 Phase 2)

  • Step 4: SDK 의존성 제거
npm uninstall @portone/browser-sdk
  • Step 5: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -iE 'portone|PaymentButton|payment-channels|@/lib/products' app lib
# 기대: 0건
npm test && npm run build
  • Step 6: 커밋
git add -A
git commit -m "chore(phase0): PortOne 잔재 제거 — 계좌이체 단일 소스 확정, saju 결제 CTA 제거"

Task 4: 죽은 페이지 4종 + 전이 고아 삭제

Files:

  • Delete: app/work/page.tsx (/work/outsourcing 리다이렉트에 가려짐. app/work/layout.tsx는 유지)
  • Delete: app/work/freelance/ (page.tsx + layout.tsx — 디렉토리째)
  • Delete: app/work/website/page.tsx (app/work/website/layout.tsxsamples/는 유지)
  • Delete: app/music/packs/ (page.tsx + layout.tsx — 디렉토리째)
  • Delete: app/components/ContactForm.tsx (유일 소비처가 죽은 /work/freelance)
  • Delete: lib/freelance-portfolio.ts (소비처가 죽은 /work·/work/freelance뿐)

Interfaces:

  • Consumes: 없음

  • Produces: 없음. /work, /work/freelance, /work/website, /music/packs URL은 리다이렉트가 계속 처리

  • Step 1: 파일 삭제

git rm app/work/page.tsx app/work/website/page.tsx
git rm -r app/work/freelance app/music/packs
git rm app/components/ContactForm.tsx lib/freelance-portfolio.ts
  • Step 2: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -E "ContactForm|freelance-portfolio" app lib
# 기대: 0건
npm test && npm run build
# 빌드 후 확인: /work/website/samples/* 8종이 라우트 목록에 존재해야 함
  • Step 3: 커밋
git add -A
git commit -m "chore(phase0): redirect에 가린 죽은 페이지 4종 + 전이 고아(ContactForm·freelance-portfolio) 제거"

Task 5: deepfield 잔재 + three 의존성 삭제

Files:

  • Delete: app/components/deepfield/HeroField.tsx (import 0회)
  • Delete: app/components/deepfield/useFieldMode.ts (HeroField 전용)
  • Delete: lib/deepfield-mode.ts, lib/__tests__/deepfield-mode.test.ts
  • Modify: package.json (three 제거 — 유일 소비처가 HeroField.tsx:5,166)

Interfaces:

  • Consumes: 없음

  • Produces: 없음. deepfield/{ScrollReveal,ShowcaseGrid,ShowcaseCard,CountUp}.tsx는 활성이므로 유지

  • Step 1: 파일 삭제

git rm app/components/deepfield/HeroField.tsx app/components/deepfield/useFieldMode.ts \
  lib/deepfield-mode.ts lib/__tests__/deepfield-mode.test.ts
  • Step 2: three 의존성 제거
npm uninstall three
grep -rn "from 'three'" app lib   # 기대: 0건
  • Step 3: 테스트·빌드
npm test        # 기대: deepfield-mode.test 제외된 채 전체 PASS
npm run build
  • Step 4: 커밋
git add -A
git commit -m "chore(phase0): deepfield 파티클 잔재 3파일 + three 의존성 제거"

Task 6: 고아 API 삭제

Files:

  • Delete: app/api/track/[token]/route.ts (추적 페이지가 Supabase 직접 조회 — app/track/[token]/page.tsx:16 주석 확인됨. 페이지는 유지)
  • Delete: app/api/saju/lotto/route.ts (프론트 fetch 0회, 외부 saju-engine 전용)

Interfaces:

  • Consumes: 없음

  • Produces: 없음. /track/[token] 페이지 동작 불변

  • Step 1: 파일 삭제

git rm -r "app/api/track" "app/api/saju/lotto"

(주의: app/api/saju/analyze·app/api/saju/save-interpretation은 활성 — saju 디렉토리째 삭제 금지)

  • Step 2: 잔존 참조 확인 후 테스트·빌드
grep -rn --include='*.ts' --include='*.tsx' -E "api/track|api/saju/lotto" app lib
# 기대: 0건
npm test && npm run build
  • Step 3: 커밋
git add -A
git commit -m "chore(phase0): 고아 API 제거 — track/[token](페이지 직접조회로 대체됨)·saju/lotto"

Task 7: DB 마이그레이션 + CLAUDE.md 정합화 + 최종 스윕

Files:

  • Create: supabase/migrations/2026-07-02-phase0-cleanup.sql
  • Modify: CLAUDE.md (삭제된 기능 서술 제거)

Interfaces:

  • Consumes: Task 1~6 완료 상태

  • Produces: DB 스키마와 코드의 정합. 마이그레이션은 클라우드+NAS 양쪽 수동 적용 항목으로 CEO에 안내

  • Step 1: 마이그레이션 파일 작성

supabase/migrations/2026-07-02-phase0-cleanup.sql:

-- Phase 0 정리 (2026-07-02): 비전 제외 기능의 테이블·설정 제거
-- 적용 대상: 클라우드 Supabase + NAS self-host 양쪽 (운영 규칙)
-- survey_responses(gyeol)는 의도적 보존 — 건드리지 않음

DROP TABLE IF EXISTS questionnaire_responses;
DROP TABLE IF EXISTS ebay_search_history;
DROP TABLE IF EXISTS subscriptions;

DELETE FROM service_settings WHERE id = 'packages';
  • Step 2: CLAUDE.md 갱신

CLAUDE.md에서:

  • 숨김 서비스 표에서 /packages 행 삭제

  • 파일 구조 트리에서 payment/ 항목과 "PortOne 연동 (보존 전용, 미활성)" 서술 삭제

  • 결제 플로우 섹션의 "PG(PortOne) 코드는 products.pay_method 플래그 기반으로 보존만, 현재 미활성" 불릿 삭제

  • 운영 주의사항 등 나머지는 유지

  • 파일 구조·표 어디에도 questionnaire/documents/packages/subscription 서술이 남지 않도록 검색(grep -n "PortOne\|packages\|questionnaire\|subscription" CLAUDE.md) 후 정리

  • Step 3: 최종 잔존 참조 스윕

grep -rn --include='*.ts' --include='*.tsx' -iE \
  "portone|PaymentButton|payment-channels|saas-catalog|ebay|questionnaire|from\('subscriptions'\)|freelance-portfolio|HeroField|useFieldMode|deepfield-mode|from 'three'" \
  app lib scripts
# 기대: 0건
grep -n "cheerio\|three\|portone" package.json
# 기대: 0건
  • Step 4: 전체 테스트·빌드
npm test        # 기대: 전체 PASS
npm run build   # 기대: 빌드 성공
  • Step 5: 커밋
git add supabase/migrations/2026-07-02-phase0-cleanup.sql CLAUDE.md
git commit -m "chore(phase0): DB 마이그레이션(DROP 3테이블+packages 행) + CLAUDE.md 정합화"
  • Step 6: CEO 안내 사항 정리 (구현 아님, 보고)

  • 마이그레이션 SQL을 클라우드 Supabase + NAS self-host 양쪽에 수동 적용 필요

  • Vercel 대시보드에서 기존 cron(subscription-expiry) 잔재 확인 (vercel.json 삭제로 다음 배포 시 자동 해제)

  • 배포는 별도 지시 시 진행


검증 요약 (전 Task 공통)

검증 명령 기대
단위 테스트 npm test product-access·request-status·showcase 등 전체 PASS
빌드 npm run build standalone 빌드 성공
잔존 참조 Task별 grep 0건
라우트 보존 빌드 출력 /work/website/samples/* 8종, /gyeol, saju·music 라우트 존재