docs: Sync documentation from private repository
This commit is contained in:
parent
76ae52ab2c
commit
b56c056e43
87
API_SPEC.md
87
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
|
## Topic API
|
||||||
|
|
||||||
### 1. POST `/topic/available` - 사용 가능한 주제 목록
|
### 1. POST `/topic/available` - 사용 가능한 주제 목록
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# 라온누리 - 데이터 모델 및 스키마
|
# 라온누리 - 데이터 모델 및 스키마
|
||||||
|
|
||||||
> 최종 업데이트: 2025-11-27 (팀 커버 이미지 시스템)
|
> 최종 업데이트: 2025-11-28 (댓글 시스템 추가)
|
||||||
|
|
||||||
이 문서는 Firestore 데이터베이스 및 Firebase Realtime Database 구조와 TypeScript 타입 정의를 설명합니다.
|
이 문서는 Firestore 데이터베이스 및 Firebase Realtime Database 구조와 TypeScript 타입 정의를 설명합니다.
|
||||||
|
|
||||||
@ -18,6 +18,8 @@ firestore
|
|||||||
├── users/ # ✅ 사용자 프로필 및 메타데이터
|
├── users/ # ✅ 사용자 프로필 및 메타데이터
|
||||||
├── writings/ # ✅ 작성한 글
|
├── writings/ # ✅ 작성한 글
|
||||||
├── topics/ # ✅ 글쓰기 주제
|
├── topics/ # ✅ 글쓰기 주제
|
||||||
|
├── comments/ # 🆕 댓글 (계층 구조 지원)
|
||||||
|
├── userReactions/ # 🆕 댓글 반응
|
||||||
├── patternAnalyses/ # ✅ 패턴 분석 결과 (contentHash 캐싱)
|
├── patternAnalyses/ # ✅ 패턴 분석 결과 (contentHash 캐싱)
|
||||||
├── lessons/ # 🔜 학습 레슨
|
├── lessons/ # 🔜 학습 레슨
|
||||||
├── stickers/ # 🔜 스티커 마스터 데이터
|
├── stickers/ # 🔜 스티커 마스터 데이터
|
||||||
@ -389,6 +391,10 @@ interface Writing {
|
|||||||
// 상태
|
// 상태
|
||||||
status: 'draft' | 'published'; // 임시저장/발행
|
status: 'draft' | 'published'; // 임시저장/발행
|
||||||
commentCount: number; // 🆕 댓글 총 개수 (기본값: 0)
|
commentCount: number; // 🆕 댓글 총 개수 (기본값: 0)
|
||||||
|
|
||||||
|
// 공개 설정
|
||||||
|
teamId?: string; // 팀 주제로 작성 시 자동 설정
|
||||||
|
visibility?: 'public' | 'team' | 'private'; // 🆕 공개 범위 (기본: private)
|
||||||
|
|
||||||
// 🆕 AI 분석 결과 (저장 시 자동 생성)
|
// 🆕 AI 분석 결과 (저장 시 자동 생성)
|
||||||
analysis?: WritingAnalysis; // AI 분석 결과 (선택적)
|
analysis?: WritingAnalysis; // AI 분석 결과 (선택적)
|
||||||
@ -429,6 +435,7 @@ interface Writing {
|
|||||||
"charCount": 450,
|
"charCount": 450,
|
||||||
|
|
||||||
"status": "published",
|
"status": "published",
|
||||||
|
"visibility": "public",
|
||||||
|
|
||||||
"analysis": {
|
"analysis": {
|
||||||
"score": 85,
|
"score": 85,
|
||||||
@ -465,6 +472,7 @@ interface Writing {
|
|||||||
- `userId` + `createdAt` (복합 인덱스, 내림차순)
|
- `userId` + `createdAt` (복합 인덱스, 내림차순)
|
||||||
- `topicId` + `createdAt` (복합 인덱스, 내림차순)
|
- `topicId` + `createdAt` (복합 인덱스, 내림차순)
|
||||||
- `status` + `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}` (구현 예정)
|
**컬렉션**: `lessons/{lessonId}` (구현 예정)
|
||||||
|
|
||||||
@ -679,7 +750,7 @@ interface Exercise {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Sticker (스티커) 🔜
|
## 9. Sticker (스티커) 🔜
|
||||||
|
|
||||||
**컬렉션**: `stickers/{stickerId}` (구현 예정)
|
**컬렉션**: `stickers/{stickerId}` (구현 예정)
|
||||||
|
|
||||||
@ -760,7 +831,7 @@ interface Sticker {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. UserSticker (사용자 스티커) 🔜
|
## 10. UserSticker (사용자 스티커) 🔜
|
||||||
|
|
||||||
**컬렉션**: `userStickers/{userStickerId}` (구현 예정)
|
**컬렉션**: `userStickers/{userStickerId}` (구현 예정)
|
||||||
|
|
||||||
@ -793,13 +864,13 @@ interface UserSticker {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. WritingSession (실시간 글쓰기 모니터링) 🆕
|
## 11. WritingSession (실시간 글쓰기 모니터링) 🆕
|
||||||
|
|
||||||
**데이터베이스**: Firebase Realtime Database (휘발성 데이터)
|
**데이터베이스**: Firebase Realtime Database (휘발성 데이터)
|
||||||
|
|
||||||
### Realtime DB 구조
|
### Realtime DB 구조
|
||||||
|
|
||||||
#### 9.1. monitoring (글쓰기 통계)
|
#### 11.1. monitoring (글쓰기 통계)
|
||||||
|
|
||||||
**경로**: `monitoring/{teamId}/{topicId}/{userId}`
|
**경로**: `monitoring/{teamId}/{topicId}/{userId}`
|
||||||
|
|
||||||
@ -891,7 +962,7 @@ if (!newData[userId] && prevData[userId]) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 9.2. previewRequests (미리보기 요청)
|
#### 11.2. previewRequests (미리보기 요청)
|
||||||
|
|
||||||
**경로**: `previewRequests/{userId}/{requestId}`
|
**경로**: `previewRequests/{userId}/{requestId}`
|
||||||
|
|
||||||
@ -903,7 +974,7 @@ interface PreviewRequest {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 9.3. previewResponses (미리보기 응답)
|
#### 11.3. previewResponses (미리보기 응답)
|
||||||
|
|
||||||
**경로**: `previewResponses/{requestId}`
|
**경로**: `previewResponses/{requestId}`
|
||||||
|
|
||||||
@ -977,6 +1048,7 @@ src/types/
|
|||||||
├── writing.ts # ✅ Writing 데이터 모델
|
├── writing.ts # ✅ Writing 데이터 모델
|
||||||
├── topic.ts # ✅ Topic 데이터 모델
|
├── topic.ts # ✅ Topic 데이터 모델
|
||||||
├── draft.ts # ✅ Draft 데이터 모델 (글조각)
|
├── draft.ts # ✅ Draft 데이터 모델 (글조각)
|
||||||
|
├── comment.ts # 🆕 Comment 데이터 모델
|
||||||
├── writingPattern.ts # ✅ WritingPattern 분석 데이터 모델
|
├── writingPattern.ts # ✅ WritingPattern 분석 데이터 모델
|
||||||
├── writingSession.ts # 🆕 WritingSession 실시간 모니터링 타입
|
├── writingSession.ts # 🆕 WritingSession 실시간 모니터링 타입
|
||||||
├── lesson.ts # 🔜 Lesson 관련 타입 (예정)
|
├── lesson.ts # 🔜 Lesson 관련 타입 (예정)
|
||||||
@ -985,6 +1057,7 @@ src/types/
|
|||||||
├── team.ts # Team API 타입
|
├── team.ts # Team API 타입
|
||||||
├── user.ts # User API 타입
|
├── user.ts # User API 타입
|
||||||
├── writing.ts # Writing API 타입
|
├── writing.ts # Writing API 타입
|
||||||
|
├── comment.ts # Comment API 타입
|
||||||
└── topic.ts # Topic API 타입
|
└── topic.ts # Topic API 타입
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -1077,6 +1150,12 @@ service cloud.firestore {
|
|||||||
allow write: if false; // API에서만 쓰기
|
allow write: if false; // API에서만 쓰기
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 댓글: 누구나 읽기 가능, 쓰기는 서버에서만
|
||||||
|
match /comments/{commentId} {
|
||||||
|
allow read: if request.auth != null;
|
||||||
|
allow write: if false;
|
||||||
|
}
|
||||||
|
|
||||||
// 주제: 모든 인증된 사용자가 읽을 수 있음
|
// 주제: 모든 인증된 사용자가 읽을 수 있음
|
||||||
match /topics/{topicId} {
|
match /topics/{topicId} {
|
||||||
allow read: if request.auth != null;
|
allow read: if request.auth != null;
|
||||||
@ -1122,4 +1201,4 @@ service cloud.firestore {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
© 2024 BlueNovaLab. All rights reserved.
|
© 2024 BlueNovaLab. All rights reserved.
|
||||||
|
|||||||
@ -305,7 +305,9 @@
|
|||||||
| **유저 홈** | `/[locale]/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, **최근 활동 (최근 글 3개 표시)**<br>비로그인 시 `/`로 자동 리다이렉트<br>정식 계정은 "내 팀" 카드 추가 표시<br>🆕 **WritingCard Grid, "모두 보기" 버튼**<br>🆕 **전체 번역 완료** (웰컴 메시지, 모든 액션 카드) | ✅ 완료 |
|
| **유저 홈** | `/[locale]/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, **최근 활동 (최근 글 3개 표시)**<br>비로그인 시 `/`로 자동 리다이렉트<br>정식 계정은 "내 팀" 카드 추가 표시<br>🆕 **WritingCard Grid, "모두 보기" 버튼**<br>🆕 **전체 번역 완료** (웰컴 메시지, 모든 액션 카드) | ✅ 완료 |
|
||||||
| **내 글 모음** | `/[locale]/writings` | 🆕 **전체 글 목록 페이지** | 🆕 **사용자의 모든 글 표시 (Grid)**<br>🆕 **정렬 Select (최신순/오래된순)**<br>🆕 **WritingCard 컴포넌트 사용**<br>🆕 **Empty state 처리**<br>🆕 **전체 번역 완료** (ko/en/ja) | ✅ 완료 |
|
| **내 글 모음** | `/[locale]/writings` | 🆕 **전체 글 목록 페이지** | 🆕 **사용자의 모든 글 표시 (Grid)**<br>🆕 **정렬 Select (최신순/오래된순)**<br>🆕 **WritingCard 컴포넌트 사용**<br>🆕 **Empty state 처리**<br>🆕 **전체 번역 완료** (ko/en/ja) | ✅ 완료 |
|
||||||
| **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)<br>🆕 **SNS 공유 미리보기** (카카오톡/페이스북)<br>🆕 **서버 데이터 로딩** (Firebase Admin SDK)<br>제목, 내용, 생성된 이미지 표시<br>주제 및 팀 정보 Badge<br>프롬프트 Collapsible<br>🆕 **CommentList** (Client Component)<br>🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 |
|
| **글 상세보기** | `/[locale]/writing/[writingId]` | 🆕 **Server Component 기반 상세 페이지** | 🆕 **SEO 최적화** (서버에서 HTML 생성)<br>🆕 **SNS 공유 미리보기** (카카오톡/페이스북)<br>🆕 **서버 데이터 로딩** (Firebase Admin SDK)<br>제목, 내용, 생성된 이미지 표시<br>주제 및 팀 정보 Badge<br>프롬프트 Collapsible<br>🆕 **CommentList** (Client Component)<br>🆕 **BackButton** 공통 컴포넌트 사용 | ✅ 완료 |
|
||||||
| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)<br>🆕 **글 수정 기능 (URL params ?id=xxx)**<br>🆕 **수정 모드 배지 표시**<br>🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)<br>제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)<br>🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼<br>🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)<br>🆕 **저장 시 AI 분석** (실시간 분석 제거, 저장 버튼 클릭 시 분석 수행)<br>🆕 **분석 결과 DB 저장** (WritingAnalysis + spellingErrors + contentHash)<br>템플릿 미리채우기 (제목/내용), Firestore 저장<br>비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 |
|
| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)<br>🆕 **글 수정 기능 (URL params ?id=xxx)**<br>🆕 **수정 모드 배지 표시**<br>🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)<br>제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)<br>🆕 **다중 글조각 관리** (최대 10개), "저장된 글조각" 버튼<br>🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)<br>🆕 **저장 플로우 개편** (저장 → 백그라운드 분석 → `/imageUpload` 리다이렉트)<br>🆕 **GenerateImageDialog 제거** (새 플로우로 대체)<br>템플릿 미리채우기 (제목/내용), Firestore 저장<br>비로그인도 접근 가능 (저장 시 로그인 유도) | ✅ 완료 |
|
||||||
|
| **이미지 업로드/선택** | `/[locale]/imageUpload` | 🆕 **이미지 소스 선택 페이지 (글 저장 후 자동 이동)** | 🆕 **2가지 선택지**: AI 생성 (장면 추출 + 선택 + 생성) / 직접 업로드<br>🆕 **드래그앤드롭 파일 업로드** (미리보기, 5MB 제한, JPEG/PNG/WebP)<br>🆕 **Canvas API 클라이언트 사이드 리사이즈** (1920x1080 최대, 85% 품질)<br>🆕 **Firebase Storage 업로드** (WritingManager.uploadUserImage)<br>🆕 **이미 이미지 있으면 자동으로 `/interaction` 리다이렉트**<br>🆕 **다국어 지원** (imageUpload namespace, ko/en/ja) | ✅ 완료 |
|
||||||
|
| **인터랙션 편집** | `/[locale]/interaction` | 🆕 **이미지 왜곡 편집 페이지 (이미지 생성/업로드 후 이동)** | 🆕 **이미지 없으면 `/imageUpload` 자동 리다이렉트**<br>왜곡 영역 편집 (EditorCanvas, DistortionArea)<br>모션 프리셋 선택 (horizontal, vertical, rotate, pulse 등)<br>물리 설정 (stiffness, damping, mass)<br>에디터/인터랙션 모드 전환 (Switch)<br>저장 시 Writing.distortionAreas 업데이트 | ✅ 완료 |
|
||||||
| **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트<br>팀/학생 생성 테스트<br>학생 로그인 테스트<br>authStore 상태 확인 | 🔜 예정 |
|
| **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트<br>팀/학생 생성 테스트<br>학생 로그인 테스트<br>authStore 상태 확인 | 🔜 예정 |
|
||||||
| **공개 팀 목록** | `/[locale]/team/all` | 🆕 **공개 팀 둘러보기** | 🆕 **공개된 팀 목록 표시** (isPublic=true)<br>🆕 **TeamCard 그리드** (글래스모피즘)<br>🆕 **페이지네이션** (커서 기반)<br>🆕 **Navbar "공개 팀" 메뉴** | ✅ 완료 |
|
| **공개 팀 목록** | `/[locale]/team/all` | 🆕 **공개 팀 둘러보기** | 🆕 **공개된 팀 목록 표시** (isPublic=true)<br>🆕 **TeamCard 그리드** (글래스모피즘)<br>🆕 **페이지네이션** (커서 기반)<br>🆕 **Navbar "공개 팀" 메뉴** | ✅ 완료 |
|
||||||
| **내 팀 목록** | `/[locale]/team` | 내가 만든/참여한 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)<br>"새 팀 만들기" 버튼 | ✅ 완료 |
|
| **내 팀 목록** | `/[locale]/team` | 내가 만든/참여한 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)<br>"새 팀 만들기" 버튼 | ✅ 완료 |
|
||||||
@ -677,6 +679,18 @@
|
|||||||
- `requestPreview(targetUserId)`: 미리보기 요청 (Promise 반환)
|
- `requestPreview(targetUserId)`: 미리보기 요청 (Promise 반환)
|
||||||
- `listenForPreviewRequests(onRequestCallback)`: 미리보기 요청 리스너 (학생용)
|
- `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 (서버리스 백엔드)
|
### 🆕 `functions/` - Firebase Cloud Functions (서버리스 백엔드)
|
||||||
@ -848,6 +862,7 @@ firebase functions:log --only cleanupExpiredReservations
|
|||||||
**Auth Store 아키텍처 (2025-11-07 단순화)**:
|
**Auth Store 아키텍처 (2025-11-07 단순화)**:
|
||||||
- ✅ **user** - Firebase Auth 기반 통합 사용자 (익명 + 정식 계정)
|
- ✅ **user** - Firebase Auth 기반 통합 사용자 (익명 + 정식 계정)
|
||||||
- ✅ **isAuthenticated** - 로그인 여부 (익명 포함)
|
- ✅ **isAuthenticated** - 로그인 여부 (익명 포함)
|
||||||
|
- ✅ **isLoading** - 🆕 **초기값 `true`로 변경 (2025-11-28)** - Auth 초기화 완료 전 리다이렉트 방지
|
||||||
- ✅ **user.isAnonymous** - 익명/정식 계정 구분
|
- ✅ **user.isAnonymous** - 익명/정식 계정 구분
|
||||||
- ✅ **loginAsUser()** - 팀 코드 로그인 (PIN 제거)
|
- ✅ **loginAsUser()** - 팀 코드 로그인 (PIN 제거)
|
||||||
- ✅ **linkWithEmail()** - 이메일 계정 연결 (신규 계정 생성)
|
- ✅ **linkWithEmail()** - 이메일 계정 연결 (신규 계정 생성)
|
||||||
|
|||||||
@ -121,6 +121,13 @@ src/
|
|||||||
- 실시간 글자수/단어수 카운터
|
- 실시간 글자수/단어수 카운터
|
||||||
- LocalStorage 자동 저장
|
- LocalStorage 자동 저장
|
||||||
|
|
||||||
|
### ✅ 댓글 및 피드백 시스템
|
||||||
|
|
||||||
|
- 계층형 댓글 구조 (대댓글 지원)
|
||||||
|
- 칭찬 및 격려 중심의 반응(Reaction) 시스템
|
||||||
|
- 작성자, 글 주인, 선생님(팀 소유자) 권한 관리
|
||||||
|
- 실시간 업데이트 및 알림
|
||||||
|
|
||||||
### ✅ UI/UX
|
### ✅ UI/UX
|
||||||
|
|
||||||
- 다크 모드 지원
|
- 다크 모드 지원
|
||||||
|
|||||||
@ -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** |
|
| **팀 커버 이미지 시스템** | **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** |
|
| **팀 멤버 아바타 표시 개선** | **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** |
|
| **채점 시스템 전면 개편** | **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)** |
|
| ~~팀 관리 기능~~ | ~~팀 생성, 멤버 관리~~ | ~~🔴 높음~~ | ✅ **백엔드 완료 (Phase 1)** |
|
||||||
| ~~팀 코드 시스템~~ | ~~한글 코드, Anonymous Auth~~ | ~~🔴 높음~~ | ✅ **백엔드 완료 (Phase 1)** |
|
| ~~팀 코드 시스템~~ | ~~한글 코드, Anonymous Auth~~ | ~~🔴 높음~~ | ✅ **백엔드 완료 (Phase 1)** |
|
||||||
|
| ~~피드백 시스템~~ | ~~글에 댓글/평점 작성~~ | ~~🔴 높음~~ | ✅ **구현 완료 (2025-11-28)** |
|
||||||
| 부모님 모드 | 자녀 진행 상황 대시보드 (정식 계정 연결 활용) | 🔴 높음 | ⏳ 예정 |
|
| 부모님 모드 | 자녀 진행 상황 대시보드 (정식 계정 연결 활용) | 🔴 높음 | ⏳ 예정 |
|
||||||
| 피드백 시스템 | 글에 댓글/평점 작성 | 🔴 높음 | ⏳ 예정 |
|
|
||||||
| 학습 리포트 | 주간/월간 활동 리포트 | 🟡 중간 | ⏳ 예정 |
|
| 학습 리포트 | 주간/월간 활동 리포트 | 🟡 중간 | ⏳ 예정 |
|
||||||
| 목표 설정 | 일일/주간 목표 설정 | 🟢 낮음 | ⏳ 예정 |
|
| 목표 설정 | 일일/주간 목표 설정 | 🟢 낮음 | ⏳ 예정 |
|
||||||
| 관리자 패널 | 주제/레슨 관리 UI | 🔴 높음 | ⏳ 예정 |
|
| 관리자 패널 | 주제/레슨 관리 UI | 🔴 높음 | ⏳ 예정 |
|
||||||
|
|||||||
10
SECURITY.md
10
SECURITY.md
@ -152,6 +152,16 @@ service cloud.firestore {
|
|||||||
allow create: if request.auth != null;
|
allow create: if request.auth != null;
|
||||||
allow update, delete: if request.auth.uid == resource.data.userId;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
191
TECH_STACK.md
191
TECH_STACK.md
@ -18,14 +18,14 @@
|
|||||||
|
|
||||||
| 기술 | 버전 | 용도 |
|
| 기술 | 버전 | 용도 |
|
||||||
|-----|------|------|
|
|-----|------|------|
|
||||||
| **Chakra UI** | v3.28.0 | 컴포넌트 라이브러리 |
|
| **Chakra UI** | v3.30.0 | 컴포넌트 라이브러리 |
|
||||||
| **@chakra-ui/charts** | latest | 🆕 **차트 컴포넌트** (Sparkline, Area/Bar/Line 차트) |
|
| **@chakra-ui/charts** | v3.29.0 | 🆕 **차트 컴포넌트** (Sparkline, Area/Bar/Line 차트) |
|
||||||
| **Recharts** | latest | 🆕 **차트 라이브러리** (Chakra Charts 내부 사용) |
|
| **Recharts** | v3.4.1 | 🆕 **차트 라이브러리** (Chakra Charts 내부 사용) |
|
||||||
| **Emotion** | 11.14.0 | CSS-in-JS |
|
| **Emotion** | 11.14.0 | CSS-in-JS |
|
||||||
| **Framer Motion** | 12.23.24 | 애니메이션 라이브러리 |
|
| **Framer Motion** | 12.23.24 | 애니메이션 라이브러리 |
|
||||||
| **React Icons** | 5.5.0 | 아이콘 세트 |
|
| **React Icons** | 5.5.0 | 아이콘 세트 |
|
||||||
| **Tiptap** | latest | 리치 텍스트 에디터 |
|
| **Tiptap** | v3.9.1 | 리치 텍스트 에디터 |
|
||||||
| **next-intl** | latest | 🆕 **다국어 지원 (i18n)** |
|
| **next-intl** | v4.5.2 | 🆕 **다국어 지원 (i18n)** |
|
||||||
|
|
||||||
### Backend & Database
|
### 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
|
### Charts
|
||||||
|
|
||||||
| 기술 | 버전 | 용도 |
|
| 기술 | 버전 | 용도 |
|
||||||
|-----|------|------|
|
|-----|------|------|
|
||||||
| **@chakra-ui/charts** | latest | 🆕 **Chakra UI 차트 컴포넌트** (실시간 모니터링 그래프) |
|
| **@chakra-ui/charts** | v3.29.0 | 🆕 **Chakra UI 차트 컴포넌트** (실시간 모니터링 그래프) |
|
||||||
| **recharts** | latest | 🆕 **차트 라이브러리** (Area, Line, Bar 차트) |
|
| **recharts** | v3.4.1 | 🆕 **차트 라이브러리** (Area, Line, Bar 차트) |
|
||||||
|
|
||||||
### State Management
|
### State Management
|
||||||
|
|
||||||
@ -135,25 +135,7 @@ import { Navbar } from "@/components/navigation/Navbar";
|
|||||||
|
|
||||||
### Firebase Config
|
### Firebase Config
|
||||||
|
|
||||||
Firebase 설정은 `src/config/firebase.ts`에 직접 하드코딩되어 있습니다:
|
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); // 🆕
|
|
||||||
```
|
|
||||||
|
|
||||||
**보안 참고**:
|
**보안 참고**:
|
||||||
- Public API Key는 클라이언트 SDK 표준 방식 (Firebase 프로젝트 설정에서 도메인 제한)
|
- Public API Key는 클라이언트 SDK 표준 방식 (Firebase 프로젝트 설정에서 도메인 제한)
|
||||||
@ -185,103 +167,7 @@ NEXT_PUBLIC_API_URL=/api
|
|||||||
|
|
||||||
### Firestore 데이터베이스
|
### Firestore 데이터베이스
|
||||||
|
|
||||||
```
|
상세한 데이터베이스 스키마와 모델 정의는 [DATA_MODELS.md](./DATA_MODELS.md) 문서를 참조하세요.
|
||||||
프로젝트 루트
|
|
||||||
└── 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/` 🔜 - 사용자별 스티커 획득 기록
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -487,24 +373,71 @@ const nickname = teamManager.getMemberNickname(team, uid, user?.name);
|
|||||||
├─> 저장 상태 표시 (저장 중 → 저장됨 → 시간)
|
├─> 저장 상태 표시 (저장 중 → 저장됨 → 시간)
|
||||||
└─> 하단 고정 버튼 (취소, 저장)
|
└─> 하단 고정 버튼 (취소, 저장)
|
||||||
|
|
||||||
4. 저장 버튼 클릭
|
4. 저장 버튼 클릭 (🆕 2025-11-28 플로우 개편)
|
||||||
├─> 미인증 시: 로그인 다이얼로그 표시
|
├─> 미인증 시: 로그인 다이얼로그 표시
|
||||||
└─> 인증 시:
|
└─> 인증 시:
|
||||||
└─> writingManager.createWriting() 호출
|
└─> writingManager.createWriting() 호출
|
||||||
├─> 유효성 검사 (제목, 내용)
|
├─> 유효성 검사 (제목, 내용)
|
||||||
├─> 텍스트 통계 계산 (글자 수, 단어 수)
|
├─> 텍스트 통계 계산 (글자 수, 단어 수)
|
||||||
├─> Firestore에 저장
|
├─> 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() - 새 글 작성
|
├─> createWriting() - 새 글 작성
|
||||||
├─> getWriting() - 글 조회
|
├─> getWriting() - 글 조회
|
||||||
├─> getUserWritings() - 사용자 글 목록
|
├─> getUserWritings() - 사용자 글 목록
|
||||||
├─> getRecentWritings() - 최근 글 목록
|
├─> getRecentWritings() - 최근 글 목록
|
||||||
├─> updateWriting() - 글 수정
|
├─> updateWriting() - 글 수정
|
||||||
└─> deleteWriting() - 글 삭제
|
├─> deleteWriting() - 글 삭제
|
||||||
|
├─> 🆕 **uploadUserImage(writingId, file)** - 사용자 이미지 업로드 (클라이언트 사이드)
|
||||||
|
└─> 🆕 **analyzeWritingBackground(writingId, locale)** - 백그라운드 분석 (fire-and-forget)
|
||||||
|
|
||||||
6. DraftManager (클라이언트 전용)
|
8. DraftManager (클라이언트 전용)
|
||||||
├─> saveDraft() - 글조각 저장 (최대 10개, FIFO)
|
├─> saveDraft() - 글조각 저장 (최대 10개, FIFO)
|
||||||
├─> getDraft() - 글조각 조회
|
├─> getDraft() - 글조각 조회
|
||||||
├─> getAllDrafts() - 전체 글조각 목록
|
├─> getAllDrafts() - 전체 글조각 목록
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user