From b56c056e430777e8bb990f3eefc46639ab6a5355 Mon Sep 17 00:00:00 2001 From: Documentation Bot Date: Fri, 28 Nov 2025 06:46:42 +0000 Subject: [PATCH] docs: Sync documentation from private repository --- API_SPEC.md | 87 ++++++++++++++++++++ DATA_MODELS.md | 97 ++++++++++++++++++++-- PROJECT_STRUCTURE.md | 17 +++- README.md | 7 ++ ROADMAP.md | 7 +- SECURITY.md | 10 +++ TECH_STACK.md | 191 ++++++++++++++----------------------------- 7 files changed, 275 insertions(+), 141 deletions(-) diff --git a/API_SPEC.md b/API_SPEC.md index 63afd9a..b938fc6 100644 --- a/API_SPEC.md +++ b/API_SPEC.md @@ -1246,6 +1246,93 @@ await teamManager.removeMember(teamId, currentUser.uid); --- +## Comment API + +### 1. GET `/comment/writing/:writingId` - 댓글 목록 조회 +실제 URL: `GET /api/comment/writing/:writingId` + +**인증**: 선택적 (현재 사용자 반응 확인용) + +**Response**: +```typescript +{ + success: true, + data: { + comments: CommentWithReplies[]; + totalCount: number; + } +} +``` + +**특징**: +- 계층 구조 (댓글 + 답글) 반환 +- 작성자 정보 (displayName, photoURL) 포함 +- 현재 사용자의 반응 포함 (로그인 시) + +### 2. POST `/comment/writing/:writingId` - 댓글 작성 +실제 URL: `POST /api/comment/writing/:writingId` + +**인증**: 필수 + +**Request**: +```typescript +{ + content: string; + parentId?: string; // 답글인 경우 부모 댓글 ID +} +``` + +**Response**: +```typescript +{ + success: true, + data: { + comment: Comment; + } +} +``` + +### 3. PUT `/comment/:id` - 댓글 수정 +실제 URL: `PUT /api/comment/:id` + +**인증**: 필수 (작성자 본인만) + +**Request**: +```typescript +{ + content: string; +} +``` + +**Response**: +```typescript +{ + success: true, + data: { + comment: Comment; + } +} +``` + +### 4. DELETE `/comment/:id` - 댓글 삭제 +실제 URL: `DELETE /api/comment/:id` + +**인증**: 필수 + +**권한**: +- 작성자 본인 +- 글 작성자 (관리 차원) +- 팀 소유자 (팀 주제인 경우) + +**Response**: +```typescript +{ + success: true +} +``` + +--- + ## Topic API ### 1. POST `/topic/available` - 사용 가능한 주제 목록 diff --git a/DATA_MODELS.md b/DATA_MODELS.md index 62548e7..f251d98 100644 --- a/DATA_MODELS.md +++ b/DATA_MODELS.md @@ -1,6 +1,6 @@ # 라온누리 - 데이터 모델 및 스키마 -> 최종 업데이트: 2025-11-27 (팀 커버 이미지 시스템) +> 최종 업데이트: 2025-11-28 (댓글 시스템 추가) 이 문서는 Firestore 데이터베이스 및 Firebase Realtime Database 구조와 TypeScript 타입 정의를 설명합니다. @@ -18,6 +18,8 @@ firestore ├── users/ # ✅ 사용자 프로필 및 메타데이터 ├── writings/ # ✅ 작성한 글 ├── topics/ # ✅ 글쓰기 주제 +├── comments/ # 🆕 댓글 (계층 구조 지원) +├── userReactions/ # 🆕 댓글 반응 ├── patternAnalyses/ # ✅ 패턴 분석 결과 (contentHash 캐싱) ├── lessons/ # 🔜 학습 레슨 ├── stickers/ # 🔜 스티커 마스터 데이터 @@ -389,6 +391,10 @@ interface Writing { // 상태 status: 'draft' | 'published'; // 임시저장/발행 commentCount: number; // 🆕 댓글 총 개수 (기본값: 0) + + // 공개 설정 + teamId?: string; // 팀 주제로 작성 시 자동 설정 + visibility?: 'public' | 'team' | 'private'; // 🆕 공개 범위 (기본: private) // 🆕 AI 분석 결과 (저장 시 자동 생성) analysis?: WritingAnalysis; // AI 분석 결과 (선택적) @@ -429,6 +435,7 @@ interface Writing { "charCount": 450, "status": "published", + "visibility": "public", "analysis": { "score": 85, @@ -465,6 +472,7 @@ interface Writing { - `userId` + `createdAt` (복합 인덱스, 내림차순) - `topicId` + `createdAt` (복합 인덱스, 내림차순) - `status` + `createdAt` (복합 인덱스) +- `teamId` + `visibility` (복합 인덱스, 팀 공개 글 조회용) --- @@ -565,7 +573,70 @@ interface Topic { --- -## 6. Lesson (학습 레슨) 🔜 +## 6. Comment (댓글) 🆕 + +**컬렉션**: `comments/{commentId}` + +### 스키마 + +```typescript +interface CommentReactions { + like: number; // 좋아요 👍 + love: number; // 최고예요 ❤️ + smile: number; // 웃겨요 😂 + clap: number; // 멋져요 👏 +} + +interface Comment { + id: string; // 문서 ID + writingId: string; // 글 ID + userId: string; // 작성자 UID + content: string; // 댓글 내용 (최대 500자) + + parentId: string | null; // 대댓글인 경우 부모 댓글 ID + + reactions: CommentReactions; // 반응 카운트 + + // 타임스탬프 + createdAt: Timestamp; + updatedAt: Timestamp; + isDeleted: boolean; // Soft delete +} +``` + +**특징**: +- 계층형 구조 지원 (1단계 대댓글) +- Soft delete (삭제된 댓글입니다 표시) +- 반응형 데이터 (좋아요 등) + +### 인덱스 +- `writingId` + `createdAt` (복합 인덱스) + +--- + +## 7. UserReaction (사용자 반응) 🆕 + +**컬렉션**: `userReactions/{reactionId}` + +### 스키마 + +```typescript +interface UserReaction { + id: string; // 문서 ID (commentId_userId) + commentId: string; // 댓글 ID + userId: string; // 사용자 UID + type: 'like' | 'love' | 'smile' | 'clap'; // 반응 타입 + createdAt: Timestamp; +} +``` + +**특징**: +- 사용자당 댓글 1개에 1개의 반응만 가능 (토글 방식) +- ID를 `commentId_userId`로 설정하여 중복 방지 + +--- + +## 8. Lesson (학습 레슨) 🔜 **컬렉션**: `lessons/{lessonId}` (구현 예정) @@ -679,7 +750,7 @@ interface Exercise { --- -## 7. Sticker (스티커) 🔜 +## 9. Sticker (스티커) 🔜 **컬렉션**: `stickers/{stickerId}` (구현 예정) @@ -760,7 +831,7 @@ interface Sticker { --- -## 8. UserSticker (사용자 스티커) 🔜 +## 10. UserSticker (사용자 스티커) 🔜 **컬렉션**: `userStickers/{userStickerId}` (구현 예정) @@ -793,13 +864,13 @@ interface UserSticker { --- -## 9. WritingSession (실시간 글쓰기 모니터링) 🆕 +## 11. WritingSession (실시간 글쓰기 모니터링) 🆕 **데이터베이스**: Firebase Realtime Database (휘발성 데이터) ### Realtime DB 구조 -#### 9.1. monitoring (글쓰기 통계) +#### 11.1. monitoring (글쓰기 통계) **경로**: `monitoring/{teamId}/{topicId}/{userId}` @@ -891,7 +962,7 @@ if (!newData[userId] && prevData[userId]) { } ``` -#### 9.2. previewRequests (미리보기 요청) +#### 11.2. previewRequests (미리보기 요청) **경로**: `previewRequests/{userId}/{requestId}` @@ -903,7 +974,7 @@ interface PreviewRequest { } ``` -#### 9.3. previewResponses (미리보기 응답) +#### 11.3. previewResponses (미리보기 응답) **경로**: `previewResponses/{requestId}` @@ -977,6 +1048,7 @@ src/types/ ├── writing.ts # ✅ Writing 데이터 모델 ├── topic.ts # ✅ Topic 데이터 모델 ├── draft.ts # ✅ Draft 데이터 모델 (글조각) +├── comment.ts # 🆕 Comment 데이터 모델 ├── writingPattern.ts # ✅ WritingPattern 분석 데이터 모델 ├── writingSession.ts # 🆕 WritingSession 실시간 모니터링 타입 ├── lesson.ts # 🔜 Lesson 관련 타입 (예정) @@ -985,6 +1057,7 @@ src/types/ ├── team.ts # Team API 타입 ├── user.ts # User API 타입 ├── writing.ts # Writing API 타입 + ├── comment.ts # Comment API 타입 └── topic.ts # Topic API 타입 ``` @@ -1077,6 +1150,12 @@ service cloud.firestore { allow write: if false; // API에서만 쓰기 } + // 댓글: 누구나 읽기 가능, 쓰기는 서버에서만 + match /comments/{commentId} { + allow read: if request.auth != null; + allow write: if false; + } + // 주제: 모든 인증된 사용자가 읽을 수 있음 match /topics/{topicId} { allow read: if request.auth != null; @@ -1122,4 +1201,4 @@ service cloud.firestore { --- -© 2024 BlueNovaLab. All rights reserved. \ No newline at end of file +© 2024 BlueNovaLab. All rights reserved. diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 12e695f..877ddf4 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -305,7 +305,9 @@ | **유저 홈** | `/[locale]/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, **최근 활동 (최근 글 3개 표시)**
비로그인 시 `/`로 자동 리다이렉트
정식 계정은 "내 팀" 카드 추가 표시
🆕 **WritingCard Grid, "모두 보기" 버튼**
🆕 **전체 번역 완료** (웰컴 메시지, 모든 액션 카드) | ✅ 완료 | | **내 글 모음** | `/[locale]/writings` | 🆕 **전체 글 목록 페이지** | 🆕 **사용자의 모든 글 표시 (Grid)**
🆕 **정렬 Select (최신순/오래된순)**
🆕 **WritingCard 컴포넌트 사용**
🆕 **Empty state 처리**
🆕 **전체 번역 완료** (ko/en/ja) | ✅ 완료 | | **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)
🆕 **SNS 공유 미리보기** (카카오톡/페이스북)
🆕 **서버 데이터 로딩** (Firebase Admin SDK)
제목, 내용, 생성된 이미지 표시
주제 및 팀 정보 Badge
프롬프트 Collapsible
🆕 **CommentList** (Client Component)
🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 | -| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **글 수정 기능 (URL params ?id=xxx)**
🆕 **수정 모드 배지 표시**
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
🆕 **저장 시 AI 분석** (실시간 분석 제거, 저장 버튼 클릭 시 분석 수행)
🆕 **분석 결과 DB 저장** (WritingAnalysis + spellingErrors + contentHash)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 | +| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **글 수정 기능 (URL params ?id=xxx)**
🆕 **수정 모드 배지 표시**
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
🆕 **저장 플로우 개편** (저장 → 백그라운드 분석 → `/imageUpload` 리다이렉트)
🆕 **GenerateImageDialog 제거** (새 플로우로 대체)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 | +| **이미지 업로드/선택** | `/[locale]/imageUpload` | 🆕 **이미지 소스 선택 페이지 (글 저장 후 자동 이동)** | 🆕 **2가지 선택지**: AI 생성 (장면 추출 + 선택 + 생성) / 직접 업로드
🆕 **드래그앤드롭 파일 업로드** (미리보기, 5MB 제한, JPEG/PNG/WebP)
🆕 **Canvas API 클라이언트 사이드 리사이즈** (1920x1080 최대, 85% 품질)
🆕 **Firebase Storage 업로드** (WritingManager.uploadUserImage)
🆕 **이미 이미지 있으면 자동으로 `/interaction` 리다이렉트**
🆕 **다국어 지원** (imageUpload namespace, ko/en/ja) | ✅ 완료 | +| **인터랙션 편집** | `/[locale]/interaction` | 🆕 **이미지 왜곡 편집 페이지 (이미지 생성/업로드 후 이동)** | 🆕 **이미지 없으면 `/imageUpload` 자동 리다이렉트**
왜곡 영역 편집 (EditorCanvas, DistortionArea)
모션 프리셋 선택 (horizontal, vertical, rotate, pulse 등)
물리 설정 (stiffness, damping, mass)
에디터/인터랙션 모드 전환 (Switch)
저장 시 Writing.distortionAreas 업데이트 | ✅ 완료 | | **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트
팀/학생 생성 테스트
학생 로그인 테스트
authStore 상태 확인 | 🔜 예정 | | **공개 팀 목록** | `/[locale]/team/all` | 🆕 **공개 팀 둘러보기** | 🆕 **공개된 팀 목록 표시** (isPublic=true)
🆕 **TeamCard 그리드** (글래스모피즘)
🆕 **페이지네이션** (커서 기반)
🆕 **Navbar "공개 팀" 메뉴** | ✅ 완료 | | **내 팀 목록** | `/[locale]/team` | 내가 만든/참여한 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)
"새 팀 만들기" 버튼 | ✅ 완료 | @@ -677,6 +679,18 @@ - `requestPreview(targetUserId)`: 미리보기 요청 (Promise 반환) - `listenForPreviewRequests(onRequestCallback)`: 미리보기 요청 리스너 (학생용) +**WritingManager 주요 메서드** (2025-11-28 추가): +- 🆕 `uploadUserImage(writingId, file)`: 클라이언트 사이드 이미지 업로드 + - Canvas API로 이미지 리사이즈 (1920x1080 최대, 85% 품질) + - Firebase Storage 업로드 (`uploadImage` from `@/utils/imageStorage`) + - Writing.generatedImage 필드 업데이트 + - 파일 검증 (JPEG/PNG/WebP, 5MB 제한) +- 🆕 `analyzeWritingBackground(writingId, locale)`: 백그라운드 분석 (fire-and-forget) + - 서버에 분석 요청만 전송, 응답 무시 + - `.catch(() => {})` 패턴으로 에러 무시 +- 🔒 `resizeImage(file, maxWidth, maxHeight, quality)`: private 이미지 리사이즈 헬퍼 + - Canvas API 사용, data URL 반환 + --- ### 🆕 `functions/` - Firebase Cloud Functions (서버리스 백엔드) @@ -848,6 +862,7 @@ firebase functions:log --only cleanupExpiredReservations **Auth Store 아키텍처 (2025-11-07 단순화)**: - ✅ **user** - Firebase Auth 기반 통합 사용자 (익명 + 정식 계정) - ✅ **isAuthenticated** - 로그인 여부 (익명 포함) +- ✅ **isLoading** - 🆕 **초기값 `true`로 변경 (2025-11-28)** - Auth 초기화 완료 전 리다이렉트 방지 - ✅ **user.isAnonymous** - 익명/정식 계정 구분 - ✅ **loginAsUser()** - 팀 코드 로그인 (PIN 제거) - ✅ **linkWithEmail()** - 이메일 계정 연결 (신규 계정 생성) diff --git a/README.md b/README.md index 54bf724..eae5841 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,13 @@ src/ - 실시간 글자수/단어수 카운터 - LocalStorage 자동 저장 +### ✅ 댓글 및 피드백 시스템 + +- 계층형 댓글 구조 (대댓글 지원) +- 칭찬 및 격려 중심의 반응(Reaction) 시스템 +- 작성자, 글 주인, 선생님(팀 소유자) 권한 관리 +- 실시간 업데이트 및 알림 + ### ✅ UI/UX - 다크 모드 지원 diff --git a/ROADMAP.md b/ROADMAP.md index 6b6b5d2..1830e61 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # 라온누리 - 개발 로드맵 -> 최종 업데이트: 2025-11-27 (채점 시스템 개편) +> 최종 업데이트: 2025-11-28 (댓글 시스템 구현 완료) 초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획 @@ -123,6 +123,9 @@ | **팀 커버 이미지 시스템** | **Team 타입 확장 (coverImage?: string), Firebase Storage 업로드 (adminStorage.bucket()), TeamCoverImageUploader 컴포넌트 (드래그앤드롭, 미리보기, AspectRatio 16:9, 5MB 제한 JPEG/PNG/WebP/GIF), POST/DELETE /api/team/[teamId]/cover-image (FormData 업로드, 기존 이미지 자동 삭제, makePublic, extractPathFromUrl 헬퍼), TeamManager 메서드 (uploadCoverImage, deleteCoverImage, fetch FormData, 캐시 무효화), 팀 생성 페이지 이미지 선택 (선택적), 팀 관리 페이지 즉시 업로드/삭제 (공개 설정 내), TeamCard 표시 (이미지 있으면 상단 140px, 그라데이션 오버레이), 다국어 지원 (team.coverImage namespace 16개 키, ko/en/ja)** | **2025-11-27** | | **팀 멤버 아바타 표시 개선** | **Avatar 컴포넌트 도입 (photoURL 표시), 익명 계정 LuUser 아이콘 + gray 색상, 정식 계정 FaUserGraduate 아이콘 + teal 색상, "정식계정/익명" 텍스트 표시 제거, 닉네임과 실명이 다른 경우만 실명 이탤릭 표시** | **2025-11-27** | | **채점 시스템 전면 개편** | **0~1 품질 기반 점수 (5단계: 0, 0.25, 0.5, 0.75, 1.0), 가중 평균 0~100점 변환, 계층적 설정 (기본→팀→주제 우선순위), ScoringConfig/ScoringWeights/ScoringRubric 타입, scoringConfigService.ts (설정 병합), temperature=0 일관성, TeamScoringSettings 컴포넌트 (가중치 슬라이더), TeamRubricSettings 컴포넌트 (5단계 기준 편집, 아코디언 UI), 팀 관리 페이지 통합, 다국어 지원 (ko/en/ja)** | **2025-11-27** | +| **이미지 업로드/선택 플로우 개편** | **/imageUpload 페이지 신규 생성 (AI 생성/직접 업로드 선택), WritingManager.uploadUserImage() 메서드 추가 (클라이언트 사이드 Canvas API 리사이즈, 1920x1080 최대, 85% 품질, Firebase Storage 업로드), WritingManager.analyzeWritingBackground() 메서드 추가 (fire-and-forget 패턴), Write 페이지 저장 플로우 변경 (저장 → 백그라운드 분석 → /imageUpload 리다이렉트, GenerateImageDialog 제거), Interaction 페이지 리다이렉트 로직 추가 (이미지 없으면 /imageUpload로 자동 이동), authStore.isLoading 초기값 변경 (false → true, auth 초기화 완료 전 리다이렉트 방지), 드래그앤드롭 파일 업로드 지원, 파일 검증 (JPEG/PNG/WebP, 5MB 제한), 다국어 지원 (imageUpload namespace, ko/en/ja 15개 키)** | **2025-11-28** | +| **가격정책 페이지** | **/pricing 페이지 신규 생성 (4개 플랜: Free, Classroom, Academy, School), 월간/연간 결제 토글 (20% 할인), 기능 비교 테이블, FAQ 섹션, 다국어 지원 (pricing namespace, ko/en/ja 40개 키), PricingCard 컴포넌트, Navbar "요금제" 메뉴 추가** | **2025-11-28** | +| **댓글 시스템** | **Comment 데이터 모델 (계층 구조, 반응형), CommentList 컴포넌트 (댓글/답글 표시, 작성/수정/삭제, 낙관적 업데이트), CommentItem (아바타, 시간, 메뉴), CommentInput (자동 높이 조절), API Routes 구현 (GET/POST/PUT/DELETE), 서버 권한 체크 (작성자/글작성자/팀소유자), 실시간 업데이트 (SWR 또는 리패치)** | **2025-11-28** | ### 🚧 진행 중 @@ -239,8 +242,8 @@ ProfileDialog (UserProfileButton 클릭 시 열림) |-----|------|---------|------| | ~~팀 관리 기능~~ | ~~팀 생성, 멤버 관리~~ | ~~🔴 높음~~ | ✅ **백엔드 완료 (Phase 1)** | | ~~팀 코드 시스템~~ | ~~한글 코드, Anonymous Auth~~ | ~~🔴 높음~~ | ✅ **백엔드 완료 (Phase 1)** | +| ~~피드백 시스템~~ | ~~글에 댓글/평점 작성~~ | ~~🔴 높음~~ | ✅ **구현 완료 (2025-11-28)** | | 부모님 모드 | 자녀 진행 상황 대시보드 (정식 계정 연결 활용) | 🔴 높음 | ⏳ 예정 | -| 피드백 시스템 | 글에 댓글/평점 작성 | 🔴 높음 | ⏳ 예정 | | 학습 리포트 | 주간/월간 활동 리포트 | 🟡 중간 | ⏳ 예정 | | 목표 설정 | 일일/주간 목표 설정 | 🟢 낮음 | ⏳ 예정 | | 관리자 패널 | 주제/레슨 관리 UI | 🔴 높음 | ⏳ 예정 | diff --git a/SECURITY.md b/SECURITY.md index 536e788..da1e242 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -152,6 +152,16 @@ service cloud.firestore { allow create: if request.auth != null; allow update, delete: if request.auth.uid == resource.data.userId; } + + // 댓글: 누구나 읽기 가능, 작성자만 수정, 작성자/글소유자/팀소유자 삭제 + match /comments/{commentId} { + allow read: if request.auth != null; + allow create: if request.auth != null; + allow update: if request.auth.uid == resource.data.userId; + allow delete: if request.auth.uid == resource.data.userId || + (resource.data.writingUserId == request.auth.uid) || + (resource.data.teamOwnerId == request.auth.uid); + } } } ``` diff --git a/TECH_STACK.md b/TECH_STACK.md index 0ed17d0..70ae006 100644 --- a/TECH_STACK.md +++ b/TECH_STACK.md @@ -18,14 +18,14 @@ | 기술 | 버전 | 용도 | |-----|------|------| -| **Chakra UI** | v3.28.0 | 컴포넌트 라이브러리 | -| **@chakra-ui/charts** | latest | 🆕 **차트 컴포넌트** (Sparkline, Area/Bar/Line 차트) | -| **Recharts** | latest | 🆕 **차트 라이브러리** (Chakra Charts 내부 사용) | +| **Chakra UI** | v3.30.0 | 컴포넌트 라이브러리 | +| **@chakra-ui/charts** | v3.29.0 | 🆕 **차트 컴포넌트** (Sparkline, Area/Bar/Line 차트) | +| **Recharts** | v3.4.1 | 🆕 **차트 라이브러리** (Chakra Charts 내부 사용) | | **Emotion** | 11.14.0 | CSS-in-JS | | **Framer Motion** | 12.23.24 | 애니메이션 라이브러리 | | **React Icons** | 5.5.0 | 아이콘 세트 | -| **Tiptap** | latest | 리치 텍스트 에디터 | -| **next-intl** | latest | 🆕 **다국어 지원 (i18n)** | +| **Tiptap** | v3.9.1 | 리치 텍스트 에디터 | +| **next-intl** | v4.5.2 | 🆕 **다국어 지원 (i18n)** | ### Backend & Database @@ -51,14 +51,14 @@ | 기술 | 버전 | 용도 | |-----|------|------| -| **use-debounce** | latest | React debounce hook (5초 API 호출 제한) | +| **use-debounce** | v10.0.6 | React debounce hook (5초 API 호출 제한) | ### Charts | 기술 | 버전 | 용도 | |-----|------|------| -| **@chakra-ui/charts** | latest | 🆕 **Chakra UI 차트 컴포넌트** (실시간 모니터링 그래프) | -| **recharts** | latest | 🆕 **차트 라이브러리** (Area, Line, Bar 차트) | +| **@chakra-ui/charts** | v3.29.0 | 🆕 **Chakra UI 차트 컴포넌트** (실시간 모니터링 그래프) | +| **recharts** | v3.4.1 | 🆕 **차트 라이브러리** (Area, Line, Bar 차트) | ### State Management @@ -135,25 +135,7 @@ import { Navbar } from "@/components/navigation/Navbar"; ### Firebase Config -Firebase 설정은 `src/config/firebase.ts`에 직접 하드코딩되어 있습니다: - -```typescript -// src/config/firebase.ts -const firebaseConfig = { - apiKey: "AIzaSyBXmSq9Sq81oNkEZsbcbc-YA9LO31URby8", - authDomain: "raonnuri-84830.firebaseapp.com", - databaseURL: "https://raonnuri-84830-default-rtdb.firebaseio.com", // 🆕 Realtime DB - projectId: "raonnuri-84830", - storageBucket: "raonnuri-84830.firebasestorage.app", - messagingSenderId: "962894843507", - appId: "1:962894843507:web:91d41427d4de819c47a406", - measurementId: "G-E4VKK56B8G" -}; - -export const fbAuth = getAuth(fbApp); -export const fbClient = getFirestore(fbApp); -export const fbRealtimeDb = getDatabase(fbApp); // 🆕 -``` +Firebase 설정은 `src/config/firebase.ts` 파일에 정의되어 있습니다. **보안 참고**: - Public API Key는 클라이언트 SDK 표준 방식 (Firebase 프로젝트 설정에서 도메인 제한) @@ -185,103 +167,7 @@ NEXT_PUBLIC_API_URL=/api ### Firestore 데이터베이스 -``` -프로젝트 루트 -└── firestore.rules # Firestore 보안 규칙 (예정) -``` - -**컬렉션 구조**: -- `writings/` ✅ - 작성한 글 - ```typescript - { - userId: string; - title: string; - content: string; // HTML - wordCount: number; - charCount: number; - visibility: WritingVisibility; // 🆕 PUBLIC | TEAM | PRIVATE (기본: PRIVATE) - analysis?: { // AI 분석 결과 (저장 시 자동 생성) - score: number; - breakdown: { sensory, emotion, dialogue, onomatopoeia }; - foundWords: { sensory[], emotion[], onomatopoeia[] }; - suggestions?: string[]; - spellingErrors?: SpellingError[]; - analyzedAt: Timestamp; - contentHash: string; // SHA-256(content) - }; - status: 'draft' | 'published'; - topicId?: string | null; // 주제 ID (null은 자유 주제) - createdAt: Timestamp; - updatedAt: Timestamp; - } - // 🆕 WritingVisibility enum: PUBLIC (전체 공개), TEAM (팀 내 공개), PRIVATE (비공개) - ``` -- `topics/` ✅ - 글쓰기 주제 (팀 주제 + 개인 주제) - ```typescript - { - title: string; - description: string; - category: TopicCategory; // Enum: daily | imagination | emotion | experience - difficulty: TopicDifficulty; // Enum: easy | medium | hard - ownerType: TopicOwnerType; // Enum: system | team | personal - ownerId?: string; // 팀 주제: teamId, 개인 주제: userId - keywords: string[]; - examplePrompts: string[]; - titleTemplate?: string; // 제목 템플릿 - contentTemplate?: string; // 내용 템플릿 - usageCount: number; - createdAt: Timestamp; - updatedAt: Timestamp; - createdBy: string; - isActive: boolean; - } - // 팀 주제: ownerId = teamId 직접 사용 (예: abc123) - // 유틸 함수: getTeamOwnerId(teamId), extractTeamId(ownerId) - 단순 반환 - ``` -- `classrooms/` ✅ - **팀 (팀 코드 시스템)** - ```typescript - { - code: string; // "춤추는 파란 사자" (한글 팀 코드) - name: string; // "2학년 1반" - ownerId: string; // 팀 소유자 UID - securityMode: 'simple' | 'normal' | 'open'; - requirePin: boolean; - allowAnonymousJoin: boolean; - // 🆕 공개 설정 - isPublic: boolean; // 팀 공개 여부 (기본: false) - allowPublicWritings: boolean; // 팀원 글 공개 허용 (기본: false) - description?: string; // 팀 소개 (공개 팀용) - createdAt: Timestamp; - updatedAt: Timestamp; - isActive: boolean; - } - ``` -- `students/` ✅ - **학생 계정 (독립적, Anonymous Auth 기반)** - ```typescript - { - firebaseUid: string; // Anonymous Auth UID - linkedUserId?: string; // 연결된 정식 계정 (선택적, 1:1) - name: string; - pinHash?: string; // SHA-256 해시 - classroomIds: string[]; // 다중 팀 지원 - isAnonymous: true; - createdAt: Timestamp; - lastLoginAt: Timestamp; - } - ``` -- `users/` 🔜 - 사용자 프로필 및 진행 상황 (정식 계정) - ```typescript - { - uid: string; - email: string; - ownedStudentIds: string[]; // students 컬렉션 ID 배열 - role: 'student' | 'parent' | 'teacher'; - // ... - } - ``` -- `lessons/` 🔜 - 학습 레슨 -- `stickers/` 🔜 - 스티커 마스터 데이터 -- `userStickers/` 🔜 - 사용자별 스티커 획득 기록 +상세한 데이터베이스 스키마와 모델 정의는 [DATA_MODELS.md](./DATA_MODELS.md) 문서를 참조하세요. --- @@ -487,24 +373,71 @@ const nickname = teamManager.getMemberNickname(team, uid, user?.name); ├─> 저장 상태 표시 (저장 중 → 저장됨 → 시간) └─> 하단 고정 버튼 (취소, 저장) -4. 저장 버튼 클릭 +4. 저장 버튼 클릭 (🆕 2025-11-28 플로우 개편) ├─> 미인증 시: 로그인 다이얼로그 표시 └─> 인증 시: └─> writingManager.createWriting() 호출 ├─> 유효성 검사 (제목, 내용) ├─> 텍스트 통계 계산 (글자 수, 단어 수) ├─> Firestore에 저장 - └─> LocalStorage draft 삭제 후 /home 이동 + └─> 🆕 **저장 성공 후**: + ├─> LocalStorage draft 삭제 + ├─> writingManager.analyzeWritingBackground(writingId, locale) + │ └─> Fire-and-forget 패턴 (응답 무시, .catch(() => {})) + └─> router.push(`/imageUpload?writingId=${writingId}`) -5. WritingManager API +5. 이미지 업로드/선택 플로우 (🆕 2025-11-28) + ``` + /imageUpload 페이지 (글 저장 후 자동 이동) + + ├─> 이미 이미지 있음? + │ └─> router.replace(`/interaction?writingId=${writingId}`) + │ + └─> 이미지 선택 (2가지 옵션) + ├─> AI 생성 + │ ├─> 1. 장면 추출 (SceneExtraction API) + │ ├─> 2. 장면 선택 (SceneSelector 컴포넌트) + │ ├─> 3. 프롬프트 최적화 (PromptOptimization API) + │ ├─> 4. 이미지 생성 (Imagen 4.0 Fast) + │ └─> 5. router.push(`/interaction?writingId=${writingId}`) + │ + └─> 직접 업로드 + ├─> 드래그앤드롭 또는 파일 선택 + ├─> 파일 검증 (JPEG/PNG/WebP, 5MB 제한) + ├─> 🆕 **클라이언트 사이드 리사이즈** (Canvas API) + │ ├─> 1920x1080 최대 크기 + │ ├─> 85% 품질 압축 + │ └─> data URL 생성 + ├─> Firebase Storage 업로드 + │ └─> writingManager.uploadUserImage(writingId, file) + └─> router.push(`/interaction?writingId=${writingId}`) + ``` + +6. 인터랙션 편집 플로우 (🆕 2025-11-28 리다이렉트 추가) + ``` + /interaction 페이지 + + ├─> 이미지 없음? + │ └─> router.replace(`/imageUpload?writingId=${writingId}`) + │ + └─> 이미지 있음: + ├─> 왜곡 영역 편집 (EditorCanvas) + ├─> 모션/물리 설정 조정 + ├─> 에디터/인터랙션 모드 전환 + └─> 저장 시 Writing.distortionAreas 업데이트 + ``` + +7. WritingManager API ├─> createWriting() - 새 글 작성 ├─> getWriting() - 글 조회 ├─> getUserWritings() - 사용자 글 목록 ├─> getRecentWritings() - 최근 글 목록 ├─> updateWriting() - 글 수정 - └─> deleteWriting() - 글 삭제 + ├─> deleteWriting() - 글 삭제 + ├─> 🆕 **uploadUserImage(writingId, file)** - 사용자 이미지 업로드 (클라이언트 사이드) + └─> 🆕 **analyzeWritingBackground(writingId, locale)** - 백그라운드 분석 (fire-and-forget) -6. DraftManager (클라이언트 전용) +8. DraftManager (클라이언트 전용) ├─> saveDraft() - 글조각 저장 (최대 10개, FIFO) ├─> getDraft() - 글조각 조회 ├─> getAllDrafts() - 전체 글조각 목록