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() - 전체 글조각 목록