Skip to content

Commit e5452ba

Browse files
jammy0903claude
andcommitted
refactor: Zustand selector 패턴으로 전환 + subscription 시스템 제거
## Zustand Selector Pattern - 모든 useStore() 호출을 selector 패턴으로 변경 - 예: `const { appUser } = useStore()` → `const appUser = useStore((s) => s.appUser)` - 불필요한 리렌더링 방지로 네비게이션 버그 해결 (레슨 선택 시 더블클릭 필요 문제) ## 변경된 파일들 (21개) - hooks: useCourses, useStreak, useLessonData, useLessonAnalytics - pages: ChapterLessonsPage, CoursesPage, DashboardPage, ProfilePage, QuizPage, ReportPage, etc. - layouts: Sidebar, TopBar, MainLayout - components: NicknameModal, OnboardingModal, ProtectedRoute, etc. ## Subscription 시스템 제거 - 백엔드: subscription 모듈 삭제 - 프론트엔드: SubscriptionPage, subscription 서비스 삭제 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent cb97f23 commit e5452ba

46 files changed

Lines changed: 852 additions & 2174 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ android {
77
applicationId "com.cosine.codeinsight"
88
minSdkVersion rootProject.ext.minSdkVersion
99
targetSdkVersion rootProject.ext.targetSdkVersion
10-
versionCode 2
11-
versionName "2.3.0"
10+
versionCode 4
11+
versionName "3.0.1"
1212
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1313
aaptOptions {
1414
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

android/variables.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ext {
22
minSdkVersion = 23
33
compileSdkVersion = 35
4-
targetSdkVersion = 33
4+
targetSdkVersion = 35
55
androidxActivityVersion = '1.9.2'
66
androidxAppCompatVersion = '1.7.0'
77
androidxCoordinatorLayoutVersion = '1.2.0'

packages/backend/prisma/schema.prisma

Lines changed: 0 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ model User {
3030
streak UserStreak?
3131
// 독립 퀴즈 (퀴즈 페이지용)
3232
standaloneQuizAttempts StandaloneQuizAttempt[]
33-
// 구독 & AI 사용량
34-
subscription UserSubscription?
35-
aiUsageRecords AIUsageRecord[]
36-
monthlyUsageSummaries MonthlyUsageSummary[]
3733
3834
@@map("users")
3935
}
@@ -473,109 +469,3 @@ model StandaloneQuizAttempt {
473469
@@map("standalone_quiz_attempts")
474470
}
475471

476-
// ============================================
477-
// 구독 & AI 사용량 추적 (2026-02-01 추가)
478-
// ============================================
479-
480-
// 구독 플랜 정의
481-
model SubscriptionPlan {
482-
id String @id // "free", "pro", "premium"
483-
name String @db.VarChar(50) // "무료", "Pro", "Premium"
484-
description String? @db.Text
485-
price Int @default(0) // 원화 (0 = 무료)
486-
currency String @default("KRW") @db.VarChar(3)
487-
billingPeriod String @default("monthly") @map("billing_period") @db.VarChar(20) // "monthly", "yearly"
488-
// AI 사용 제한
489-
monthlyTokenLimit Int? @map("monthly_token_limit") // null = 무제한
490-
dailyTokenLimit Int? @map("daily_token_limit") // null = 무제한
491-
// 기능 접근 권한
492-
features String[] @default([]) // ["ai_chat", "ai_explain", "priority_support"]
493-
// 메타
494-
isActive Boolean @default(true) @map("is_active")
495-
order Int @default(0) // 표시 순서
496-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
497-
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
498-
// 관계
499-
subscriptions UserSubscription[]
500-
501-
@@map("subscription_plans")
502-
}
503-
504-
// 사용자 구독 상태
505-
model UserSubscription {
506-
id String @id @default(uuid()) @db.Uuid
507-
userId String @unique @map("user_id") @db.Uuid
508-
planId String @map("plan_id")
509-
// 구독 상태
510-
status String @default("active") @db.VarChar(20) // "active", "canceled", "past_due", "trialing"
511-
// 결제 기간
512-
currentPeriodStart DateTime @map("current_period_start") @db.Timestamptz(6)
513-
currentPeriodEnd DateTime @map("current_period_end") @db.Timestamptz(6)
514-
cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end") // 기간 종료 시 해지 예약
515-
// 결제 정보
516-
paymentProvider String? @map("payment_provider") @db.VarChar(20) // "google_play", "toss", "kakaopay"
517-
externalSubscriptionId String? @map("external_subscription_id") @db.VarChar(255) // 외부 결제 시스템 ID
518-
externalCustomerId String? @map("external_customer_id") @db.VarChar(255) // 외부 고객 ID
519-
// 메타
520-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
521-
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
522-
// 관계
523-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
524-
plan SubscriptionPlan @relation(fields: [planId], references: [id])
525-
526-
@@index([status])
527-
@@index([currentPeriodEnd]) // 만료 예정 구독 조회
528-
@@index([paymentProvider, externalSubscriptionId]) // 웹훅에서 빠른 조회
529-
@@map("user_subscriptions")
530-
}
531-
532-
// AI 사용량 기록 (요청별 상세)
533-
model AIUsageRecord {
534-
id String @id @default(uuid()) @db.Uuid
535-
userId String @map("user_id") @db.Uuid
536-
// 요청 정보
537-
endpoint String @db.VarChar(50) // "chat", "explain-step", "analyze-report"
538-
model String @db.VarChar(50) // "deepseek-chat", "ollama-llama3"
539-
// 토큰 사용량
540-
promptTokens Int @map("prompt_tokens")
541-
completionTokens Int @map("completion_tokens")
542-
totalTokens Int @map("total_tokens")
543-
// 비용 (USD)
544-
cost Decimal @db.Decimal(10, 6) // $0.000175 같은 작은 값도 저장
545-
// 요청 컨텍스트
546-
lessonId String? @map("lesson_id")
547-
context String? @db.VarChar(50) // "lesson", "playground", "general"
548-
// 메타
549-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
550-
// 관계
551-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
552-
553-
@@index([userId, createdAt]) // 사용자별 시간순
554-
@@index([createdAt]) // 전체 시간순, 월별 집계
555-
@@index([userId, endpoint]) // 사용자별 엔드포인트 통계
556-
@@map("ai_usage_records")
557-
}
558-
559-
// 월별 사용량 집계 (빠른 조회용)
560-
model MonthlyUsageSummary {
561-
id String @id @default(uuid()) @db.Uuid
562-
userId String @map("user_id") @db.Uuid
563-
yearMonth String @map("year_month") @db.VarChar(7) // "2024-01"
564-
// 집계 데이터
565-
totalTokens Int @default(0) @map("total_tokens")
566-
totalCost Decimal @default(0) @map("total_cost") @db.Decimal(10, 4) // USD
567-
requestCount Int @default(0) @map("request_count")
568-
// 엔드포인트별 breakdown
569-
chatTokens Int @default(0) @map("chat_tokens")
570-
explainTokens Int @default(0) @map("explain_tokens")
571-
otherTokens Int @default(0) @map("other_tokens")
572-
// 메타
573-
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
574-
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(6)
575-
// 관계
576-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
577-
578-
@@unique([userId, yearMonth])
579-
@@index([yearMonth]) // 월별 전체 통계
580-
@@map("monthly_usage_summaries")
581-
}

packages/backend/prisma/seed.ts

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -630,64 +630,13 @@ async function seed() {
630630
console.log(` ❓ Loaded ${quizCount} quizzes`);
631631
}
632632

633-
// 8. 구독 플랜 시드
634-
console.log('\n💳 Seeding subscription plans...');
635-
const subscriptionPlans = [
636-
{
637-
id: 'free',
638-
name: '무료',
639-
description: '챕터 1-2 학습 + AI 체험',
640-
price: 0,
641-
currency: 'KRW',
642-
billingPeriod: 'monthly',
643-
monthlyTokenLimit: 50000, // 월 50,000 토큰
644-
dailyTokenLimit: 5000, // 일 5,000 토큰
645-
features: ['basic_learning', 'ai_chat_limited', 'chapters_1_2'],
646-
order: 0,
647-
},
648-
{
649-
id: 'basic',
650-
name: '베이직',
651-
description: '전체 챕터 + AI 확장',
652-
price: 2000,
653-
currency: 'KRW',
654-
billingPeriod: 'monthly',
655-
monthlyTokenLimit: 500000, // 월 500,000 토큰
656-
dailyTokenLimit: 50000, // 일 50,000 토큰
657-
features: ['basic_learning', 'ai_chat', 'ai_explain', 'all_chapters'],
658-
order: 1,
659-
},
660-
{
661-
id: 'premium',
662-
name: '프리미엄',
663-
description: '전체 챕터 + AI 무제한',
664-
price: 5000,
665-
currency: 'KRW',
666-
billingPeriod: 'monthly',
667-
monthlyTokenLimit: 2000000, // 월 2,000,000 토큰
668-
dailyTokenLimit: null, // 일일 제한 없음
669-
features: ['basic_learning', 'ai_chat', 'ai_explain', 'all_chapters', 'priority_support'],
670-
order: 2,
671-
},
672-
];
673-
674-
for (const plan of subscriptionPlans) {
675-
await prisma.subscriptionPlan.upsert({
676-
where: { id: plan.id },
677-
update: plan,
678-
create: plan,
679-
});
680-
console.log(` ✓ Plan: ${plan.name} (${plan.price}원/월)`);
681-
}
682-
683-
// 9. 결과 확인
633+
// 8. 결과 확인
684634
const stats = {
685635
languages: await prisma.language.count(),
686636
chapters: await prisma.chapter.count(),
687637
lessons: await prisma.lesson.count(),
688638
contents: await prisma.lessonContent.count(),
689639
quizzes: await prisma.quiz.count(),
690-
subscriptionPlans: await prisma.subscriptionPlan.count(),
691640
};
692641

693642
console.log('\n✅ Seeding complete!');
@@ -697,7 +646,6 @@ async function seed() {
697646
console.log(` - Lessons: ${stats.lessons}`);
698647
console.log(` - Contents: ${stats.contents}`);
699648
console.log(` - Quizzes: ${stats.quizzes}`);
700-
console.log(` - Subscription Plans: ${stats.subscriptionPlans}`);
701649
}
702650

703651
// 실행

0 commit comments

Comments
 (0)