docs: Sync documentation from private repository
This commit is contained in:
parent
aef9314f31
commit
d09f99a0cc
194
DATA_MODELS.md
194
DATA_MODELS.md
@ -1,29 +1,43 @@
|
||||
# 라온누리 - 데이터 모델 및 스키마
|
||||
|
||||
> 최종 업데이트: 2025-11-07
|
||||
> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링)
|
||||
|
||||
이 문서는 Firestore 데이터베이스 구조와 TypeScript 타입 정의를 설명합니다.
|
||||
이 문서는 Firestore 데이터베이스 및 Firebase Realtime Database 구조와 TypeScript 타입 정의를 설명합니다.
|
||||
|
||||
**참고**: API 타입 정의는 [API_SPEC.md](./API_SPEC.md)를 참조하세요.
|
||||
|
||||
---
|
||||
|
||||
## Firestore 컬렉션 구조
|
||||
## 데이터베이스 구조
|
||||
|
||||
### 컬렉션 개요
|
||||
### Firestore (영구 데이터)
|
||||
|
||||
```
|
||||
firestore
|
||||
├── teams/ # ✅ 팀 (팀 코드 시스템)
|
||||
├── students/ # ✅ 학생 계정 (Anonymous Auth)
|
||||
├── users/ # 🔜 사용자 프로필 및 진행 상황
|
||||
├── users/ # ✅ 사용자 프로필 및 메타데이터
|
||||
├── writings/ # ✅ 작성한 글
|
||||
├── topics/ # ✅ 글쓰기 주제
|
||||
├── patternAnalyses/ # ✅ 패턴 분석 결과 (contentHash 캐싱)
|
||||
├── lessons/ # 🔜 학습 레슨
|
||||
├── stickers/ # 🔜 스티커 마스터 데이터
|
||||
└── userStickers/ # 🔜 사용자별 스티커 획득 기록
|
||||
```
|
||||
|
||||
### Firebase Realtime Database (실시간 데이터)
|
||||
|
||||
```
|
||||
realtime-db
|
||||
├── monitoring/ # 🆕 실시간 글쓰기 모니터링
|
||||
│ └── {teamId}/
|
||||
│ └── {topicId}/
|
||||
│ └── {userId}/
|
||||
├── previewRequests/ # 🆕 미리보기 요청
|
||||
│ └── {userId}/
|
||||
└── previewResponses/ # 🆕 미리보기 응답
|
||||
└── {requestId}/
|
||||
```
|
||||
|
||||
**범례**:
|
||||
- ✅ 구현 완료
|
||||
- 🔜 구현 예정
|
||||
@ -613,6 +627,160 @@ interface UserSticker {
|
||||
|
||||
---
|
||||
|
||||
## 9. WritingSession (실시간 글쓰기 모니터링) 🆕
|
||||
|
||||
**데이터베이스**: Firebase Realtime Database (휘발성 데이터)
|
||||
|
||||
### Realtime DB 구조
|
||||
|
||||
#### 9.1. monitoring (글쓰기 통계)
|
||||
|
||||
**경로**: `monitoring/{teamId}/{topicId}/{userId}`
|
||||
|
||||
```typescript
|
||||
interface WritingStats {
|
||||
userId: string; // 작성자 UID
|
||||
contentLength: number; // 현재 글자 수 (공백 포함)
|
||||
wordCount: number; // 현재 단어 수
|
||||
topicId: string; // 현재 작성 중인 주제 ID
|
||||
lastUpdated: number; // 마지막 업데이트 시간 (timestamp) - 항상 변경됨
|
||||
}
|
||||
|
||||
interface SpeedDataPoint {
|
||||
speed: number; // 작성 속도 (글자/분)
|
||||
timestamp: number; // 기록 시간
|
||||
}
|
||||
|
||||
interface MonitoringData extends WritingStats {
|
||||
displayName: string; // 표시 이름
|
||||
speedHistory: SpeedDataPoint[]; // 속도 히스토리 (최근 10개)
|
||||
currentSpeed: number; // 현재 속도 (글자/분)
|
||||
preview?: string; // 미리보기 (선택적)
|
||||
}
|
||||
```
|
||||
|
||||
**예시 데이터**:
|
||||
```json
|
||||
{
|
||||
"monitoring": {
|
||||
"team_abc123": {
|
||||
"topic_xyz": {
|
||||
"user_001": {
|
||||
"userId": "user_001",
|
||||
"contentLength": 1500,
|
||||
"wordCount": 300,
|
||||
"topicId": "topic_xyz",
|
||||
"lastUpdated": 1731398400000
|
||||
},
|
||||
"user_002": {
|
||||
"userId": "user_002",
|
||||
"contentLength": 800,
|
||||
"wordCount": 160,
|
||||
"topicId": "topic_xyz",
|
||||
"lastUpdated": 1731398395000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**클라이언트 측 계산** (선생님 화면):
|
||||
```typescript
|
||||
// MonitoringData (UI용)
|
||||
{
|
||||
userId: "user_001",
|
||||
contentLength: 1500,
|
||||
wordCount: 300,
|
||||
topicId: "topic_xyz",
|
||||
lastUpdated: 1731398400000,
|
||||
displayName: "철수",
|
||||
currentSpeed: 420, // 글자/분 (클라이언트 계산)
|
||||
speedHistory: [ // 최근 10개 (클라이언트 계산)
|
||||
{ speed: 480, timestamp: 1731398370000 },
|
||||
{ speed: 420, timestamp: 1731398375000 },
|
||||
{ speed: 360, timestamp: 1731398380000 },
|
||||
{ speed: 0, timestamp: 1731398385000 }, // 멈춤!
|
||||
{ speed: 0, timestamp: 1731398390000 },
|
||||
{ speed: 420, timestamp: 1731398395000 }, // 재시작
|
||||
{ speed: 480, timestamp: 1731398400000 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 9.2. previewRequests (미리보기 요청)
|
||||
|
||||
**경로**: `previewRequests/{userId}/{requestId}`
|
||||
|
||||
```typescript
|
||||
interface PreviewRequest {
|
||||
requestedBy: string; // 요청한 관리자 UID
|
||||
timestamp: number; // 요청 시간
|
||||
requestId: string; // 고유 요청 ID
|
||||
}
|
||||
```
|
||||
|
||||
#### 9.3. previewResponses (미리보기 응답)
|
||||
|
||||
**경로**: `previewResponses/{requestId}`
|
||||
|
||||
```typescript
|
||||
interface PreviewResponse {
|
||||
content: string; // 현재 작성 중인 글 내용
|
||||
timestamp: number; // 응답 시간
|
||||
requestId: string; // 요청 ID (매칭용)
|
||||
}
|
||||
```
|
||||
|
||||
### 업데이트 주기
|
||||
|
||||
- **통계 전송**: 5초마다 (학생이 팀 주제로 작성 중일 때만)
|
||||
- **자동 정리**: `onDisconnect().remove()`로 페이지 이탈 시 자동 삭제
|
||||
- **미리보기**: 요청 시에만 (10초 타임아웃)
|
||||
|
||||
### Security Rules
|
||||
|
||||
**파일**: `database.rules.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"monitoring": {
|
||||
"$teamId": {
|
||||
"$topicId": {
|
||||
"$userId": {
|
||||
".read": "auth != null && (auth.uid == $userId || root.child('teamOwners').child($teamId).val() == auth.uid)",
|
||||
".write": "auth != null && auth.uid == $userId"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"previewRequests": {
|
||||
"$userId": {
|
||||
".read": "auth != null && auth.uid == $userId",
|
||||
".write": "auth != null"
|
||||
}
|
||||
},
|
||||
"previewResponses": {
|
||||
"$requestId": {
|
||||
".read": "auth != null",
|
||||
".write": "auth != null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**권한**:
|
||||
- 통계 쓰기: 본인만
|
||||
- 통계 읽기: 본인 + 팀 소유자
|
||||
- 미리보기 요청: 누구나 쓰기, 대상자만 읽기
|
||||
- 미리보기 응답: 누구나 읽기/쓰기 (requestId로 필터링)
|
||||
|
||||
**TypeScript**: `src/types/writingSession.ts`
|
||||
|
||||
---
|
||||
|
||||
## TypeScript 타입 정의 파일
|
||||
|
||||
### 데이터 모델 타입
|
||||
@ -622,17 +790,19 @@ interface UserSticker {
|
||||
```
|
||||
src/types/
|
||||
├── team.ts # ✅ Team 데이터 모델
|
||||
├── student.ts # ✅ Student 데이터 모델
|
||||
├── firestoreUser.ts # ✅ User 데이터 모델 (FirestoreUser, User 분리)
|
||||
├── writing.ts # ✅ Writing 데이터 모델
|
||||
├── topic.ts # ✅ Topic 데이터 모델
|
||||
├── user.ts # 🔜 User 관련 타입 (예정)
|
||||
├── draft.ts # ✅ Draft 데이터 모델 (글조각)
|
||||
├── writingPattern.ts # ✅ WritingPattern 분석 데이터 모델
|
||||
├── writingSession.ts # 🆕 WritingSession 실시간 모니터링 타입
|
||||
├── lesson.ts # 🔜 Lesson 관련 타입 (예정)
|
||||
├── sticker.ts # 🔜 Sticker 관련 타입 (예정)
|
||||
└── api/ # ✅ API Request/Response 타입
|
||||
├── team.ts # Team API 타입 (10개 엔드포인트)
|
||||
├── student.ts # Student API 타입 (13개 엔드포인트)
|
||||
├── writing.ts # Writing API 타입 (6개 엔드포인트)
|
||||
└── topic.ts # Topic API 타입 (6개 엔드포인트)
|
||||
├── team.ts # Team API 타입
|
||||
├── user.ts # User API 타입
|
||||
├── writing.ts # Writing API 타입
|
||||
└── topic.ts # Topic API 타입
|
||||
```
|
||||
|
||||
### 사용 예시
|
||||
|
||||
@ -1,10 +1,25 @@
|
||||
# 라온누리 - 프로젝트 구조
|
||||
|
||||
> 최종 업데이트: 2025-11-12 (Writing API, 패턴 분석 확장, Content Hash 캐싱)
|
||||
> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링 시스템)
|
||||
|
||||
초등학생을 위한 창작 글쓰기 교육 플랫폼
|
||||
|
||||
**최신 업데이트** (2025-11-12):
|
||||
**최신 업데이트** (2025-11-12 PM):
|
||||
- 📡 **실시간 글쓰기 모니터링 시스템**
|
||||
- Firebase Realtime Database 기반 실시간 통신 (Redis Pub/Sub 방식)
|
||||
- 팀 주제 선택 시 해당 주제로 작성 중인 학생 실시간 표시
|
||||
- 5초 주기 자동 업데이트 (글자 수, 단어 수, lastUpdated 타임스탬프)
|
||||
- **작성 속도 계산** (클라이언트 측, 글자/분 단위)
|
||||
- **Sparkline 그래프** (Area Chart, 최근 10개 데이터 포인트)
|
||||
- **인터랙티브 툴팁** (마우스 오버 시 속도 + 몇 초 전 데이터인지 표시)
|
||||
- 미리보기 요청-응답 시스템 (관리자 클릭 → 학생 응답 → Dialog 표시)
|
||||
- onDisconnect() 자동 정리 (페이지 이탈 시 세션 자동 삭제)
|
||||
- LiveWritingMonitor 컴포넌트 (주제 Select, 실시간 카드 그리드)
|
||||
- WritingSessionManager (Realtime DB 작업, 상세 디버그 로그)
|
||||
- 완전 무료 (동시 접속 100명까지, 1GB/day)
|
||||
- Security Rules로 권한 관리 (본인 쓰기, 인증된 사용자 읽기)
|
||||
|
||||
**최신 업데이트** (2025-11-12 AM):
|
||||
- ✅ **Writing API 구현 완료**
|
||||
- POST /api/writing - 글 생성 (서버에서 wordCount/charCount 자동 계산)
|
||||
- GET /api/writing/[id] - 글 조회 (작성자만 접근)
|
||||
@ -289,9 +304,25 @@
|
||||
| **TeamTopicManager** | `TeamTopicManager.tsx` | 팀 주제 목록 및 생성/삭제 UI | ✅ 완료 |
|
||||
| **SecurityLevelSelector** | `SecurityLevelSelector.tsx` | 5단계 보안 레벨 선택 (RadioCard, framer-motion 애니메이션) | ✅ 완료 |
|
||||
| **AllowListManager** | `AllowListManager.tsx` | 명단 관리 (이름/이메일 추가/제거, TagsInput) | ✅ 완료 |
|
||||
| **TopicMemberAnalysisSection** | `TopicMemberAnalysisSection.tsx` | 🆕 주제별 학생 글쓰기 분석 (Accordion, by-topic 분석) | 🚧 UI만 (API 추후 구현) |
|
||||
| **TopicMemberAnalysisSection** | `TopicMemberAnalysisSection.tsx` | 주제별 학생 글쓰기 분석 (Accordion, by-topic 분석) | 🚧 UI만 (API 추후 구현) |
|
||||
| **LiveWritingMonitor** | `LiveWritingMonitor.tsx` | 🆕 **실시간 글쓰기 모니터링** (Firebase Realtime DB, 5초 주기) | ✅ 완료 |
|
||||
|
||||
**주요 기능** (2025-11-10):
|
||||
**주요 기능**:
|
||||
- ✅ **LiveWritingMonitor** (2025-11-12):
|
||||
- 주제 선택 드롭다운 (Chakra UI Select)
|
||||
- StudentMonitorCard 컴포넌트 (개별 학생 카드)
|
||||
- 실시간 통계: 글자 수, 단어 수, 마지막 업데이트 시간 ("N초 전")
|
||||
- **작성 속도 계산** (클라이언트 측, charDiff * 12 = 글자/분)
|
||||
- **Sparkline 그래프** (@chakra-ui/charts, Area Chart)
|
||||
- 최근 10개 데이터 포인트 (speedHistory)
|
||||
- 0도 표시 (작성 멈춤 시각화)
|
||||
- Teal 색상, 투명도 30%
|
||||
- **인터랙티브 툴팁** (Recharts Tooltip)
|
||||
- 속도 값 ("N자/분")
|
||||
- 시간 정보 ("N초 전")
|
||||
- 미리보기 버튼 (Dialog로 현재 작성 중인 글 표시)
|
||||
- 실시간 구독/구독 해제 (useEffect cleanup)
|
||||
- 빈 상태 UI (작성 중인 학생 없을 때)
|
||||
- ✅ SecurityLevelSelector: RadioCard 기반, 선택 시 추가 UI 애니메이션으로 표시
|
||||
- ✅ AllowListManager: Level 2/4용 명단 관리, Enter/쉼표로 구분 입력
|
||||
- ✅ 그라데이션 배경 (RadioCard와 자연스럽게 연결)
|
||||
@ -324,7 +355,8 @@
|
||||
| **ManagerBase** | `ManagerBase.ts` | 공통 기능 (authenticatedFetch, API 호출, 캐싱) | ✅ 완료 |
|
||||
| **TeamManager** | `TeamManager.ts` | 팀 관련 API 호출 (생성, 조회, 수정, 삭제, 멤버 관리) | ✅ 완료 |
|
||||
| **UserManager** | `UserManager.ts` | 사용자 관련 API 호출 (생성, 조회, 수정, 닉네임 관리) **[NEW]** | ✅ 완료 |
|
||||
| **DraftManager** | `DraftManager.ts` | 🆕 글조각 관리 (localStorage 기반, 최대 10개, CRUD) | ✅ 완료 |
|
||||
| **DraftManager** | `DraftManager.ts` | 글조각 관리 (localStorage 기반, 최대 10개, CRUD) | ✅ 완료 |
|
||||
| **WritingSessionManager** | `WritingSessionManager.ts` | 🆕 **실시간 글쓰기 세션 관리** (Firebase Realtime DB 작업) | ✅ 완료 |
|
||||
| ~~**StudentManager**~~ | ~~`StudentManager.ts`~~ | ~~학생 관련 API 호출~~ | ⚠️ Deprecated (UserManager로 대체) |
|
||||
| **WritingManager** | `WritingManager.ts` | 글쓰기 관련 비즈니스 로직 (CRUD, 통계) | ✅ 완료 |
|
||||
| **TopicManager** | `TopicManager.ts` | 주제 관련 비즈니스 로직 (CRUD, 템플릿 처리) | ✅ 완료 |
|
||||
@ -341,6 +373,13 @@
|
||||
- ✅ **타입 안전성**: Request/Response 타입 완전 정의
|
||||
- ✅ **비즈니스 로직과 UI 레이어 분리**
|
||||
|
||||
**WritingSessionManager 주요 메서드** (2025-11-12):
|
||||
- `startMonitoring(teamId, topicId, getStatsCallback)`: 5초 주기 통계 전송 시작
|
||||
- `stopMonitoring()`: 통계 전송 중지 (페이지 이탈 시)
|
||||
- `subscribeToTopic(teamId, topicId, callback)`: 실시간 구독 (관리자용)
|
||||
- `requestPreview(targetUserId)`: 미리보기 요청 (Promise 반환)
|
||||
- `listenForPreviewRequests(onRequestCallback)`: 미리보기 요청 리스너 (학생용)
|
||||
|
||||
### 📁 `src/services/` - 데이터 레이어 (일부 deprecated)
|
||||
|
||||
| 서비스 | 파일명 | 설명 | 상태 |
|
||||
@ -371,7 +410,8 @@
|
||||
| **Team 타입** | `team.ts` | 팀 데이터 모델 (members Map), **TeamSecurityLevel Enum (1-5)** | ✅ 완료 |
|
||||
| **FirestoreUser 타입** | `firestoreUser.ts` | **FirestoreUser** (DB 저장용), **User** (UI용) 분리 | ✅ 완료 |
|
||||
| **Draft 타입** | `draft.ts` | 글조각 데이터 모델 (Draft, DraftListItem, **AnalysisHistoryItem**) | ✅ 완료 |
|
||||
| **WritingPattern 타입** | `writingPattern.ts` | 🆕 **글 작성 패턴 분석** 데이터 모델 (WritingPatternAnalysis) | ✅ 완료 |
|
||||
| **WritingPattern 타입** | `writingPattern.ts` | **글 작성 패턴 분석** 데이터 모델 (WritingPatternAnalysis) | ✅ 완료 |
|
||||
| **WritingSession 타입** | `writingSession.ts` | 🆕 **실시간 모니터링** 데이터 모델 (WritingStats, PreviewRequest/Response, MonitoringData) | ✅ 완료 |
|
||||
| ~~**User 타입**~~ | ~~`user.ts`~~ | ~~사용자 데이터 모델~~ | ⚠️ Deprecated (firestoreUser.ts로 변경) |
|
||||
| ~~**Student 타입**~~ | ~~`student.ts`~~ | ~~학생 데이터 모델~~ | ⚠️ Deprecated (user.ts로 대체) |
|
||||
| **Topic 타입** | `topic.ts` | 주제 데이터 모델, TopicCategory/Difficulty/OwnerType Enum, 팀 주제 유틸 함수 | ✅ 완료 |
|
||||
@ -452,22 +492,36 @@
|
||||
|
||||
| 파일 | 설명 | 상태 |
|
||||
|------|------|------|
|
||||
| `firebase.ts` | Firebase 초기화 및 설정 | ✅ 완료 |
|
||||
| `firebase.ts` | Firebase 초기화 및 설정 (Auth, Firestore, **Realtime DB**) | ✅ 완료 |
|
||||
| `site.ts` | 사이트 메타데이터 및 설정 | ✅ 완료 |
|
||||
|
||||
**Firebase 서비스** (2025-11-12):
|
||||
- ✅ Firebase Authentication (Email/Password, Google OAuth, Anonymous)
|
||||
- ✅ Cloud Firestore (데이터베이스)
|
||||
- ✅ Firebase Realtime Database (실시간 모니터링)
|
||||
- ✅ Firebase Analytics (분석)
|
||||
|
||||
**Firebase Config**:
|
||||
- Firebase 설정은 `src/config/firebase.ts`에 하드코딩되어 있습니다
|
||||
- Public API Key는 Firebase 프로젝트 설정에서 제한 가능
|
||||
- 환경변수 대신 코드에 직접 포함 (클라이언트 SDK 표준 방식)
|
||||
|
||||
---
|
||||
|
||||
## 디렉토리 구조 요약
|
||||
|
||||
```
|
||||
src/
|
||||
project_w/
|
||||
├── database.rules.json # 🆕 Firebase Realtime DB Security Rules
|
||||
│
|
||||
├── src/
|
||||
├── app/ # Next.js App Router
|
||||
│ ├── layout.tsx # 루트 레이아웃
|
||||
│ ├── page.tsx # 랜딩 페이지 (/) - 로그인 시 /home 리다이렉트
|
||||
│ ├── home/
|
||||
│ │ └── page.tsx # ✅ 유저 홈 페이지 (/home)
|
||||
│ ├── write/
|
||||
│ │ └── page.tsx # ✅ 글쓰기 페이지 (/write)
|
||||
│ │ └── page.tsx # ✅ 글쓰기 페이지 (/write) - 🆕 실시간 모니터링 전송
|
||||
│ ├── test/
|
||||
│ │ └── page.tsx # ✅ 테스트 페이지 (/test) - 팀 코드 시스템 테스트
|
||||
│ ├── globals.css
|
||||
@ -498,8 +552,10 @@ src/
|
||||
│ │ └── CreateTeamTopicDialog.tsx # ✅ 팀 주제 생성
|
||||
│ ├── team/ # ✅ 팀 관련
|
||||
│ │ ├── TeamTopicManager.tsx # ✅ 팀 주제 관리
|
||||
│ │ ├── SecurityLevelSelector.tsx # 🆕 보안 레벨 선택 (RadioCard, 애니메이션)
|
||||
│ │ └── AllowListManager.tsx # 🆕 명단 관리 (TagsInput)
|
||||
│ │ ├── SecurityLevelSelector.tsx # 보안 레벨 선택 (RadioCard, 애니메이션)
|
||||
│ │ ├── AllowListManager.tsx # 명단 관리 (TagsInput)
|
||||
│ │ ├── TopicMemberAnalysisSection.tsx # 주제별 학생 분석 (UI만)
|
||||
│ │ └── LiveWritingMonitor.tsx # 🆕 실시간 글쓰기 모니터링 (Realtime DB)
|
||||
│ └── [미구현]
|
||||
│ ├── lesson/ # 학습 컴포넌트
|
||||
│ ├── sticker/ # 스티커 컴포넌트
|
||||
@ -515,7 +571,11 @@ src/
|
||||
├── managers/ # ✅ 비즈니스 로직 (Manager 패턴)
|
||||
│ ├── ManagerBase.ts # 기본 Manager 클래스
|
||||
│ ├── WritingManager.ts # 글쓰기 관리
|
||||
│ ├── TopicManager.ts # ✅ 주제 관리
|
||||
│ ├── WritingSessionManager.ts # 🆕 실시간 세션 관리 (Realtime DB)
|
||||
│ ├── TopicManager.ts # 주제 관리
|
||||
│ ├── TeamManager.ts # 팀 관리
|
||||
│ ├── UserManager.ts # 사용자 관리
|
||||
│ ├── DraftManager.ts # 글조각 관리 (localStorage)
|
||||
│ └── [미구현]
|
||||
│ ├── LevelManager.ts
|
||||
│ └── StickerManager.ts
|
||||
@ -544,11 +604,15 @@ src/
|
||||
├── types/ # TypeScript 타입
|
||||
│ ├── topic.ts # ✅ 주제 관련 타입 (Enum + 유틸 함수)
|
||||
│ ├── team.ts # ✅ 팀 관련 타입 (Enum)
|
||||
│ ├── student.ts # ✅ 학생 타입
|
||||
│ ├── firestoreUser.ts # ✅ 사용자 타입 (FirestoreUser, User)
|
||||
│ ├── draft.ts # ✅ 글조각 타입
|
||||
│ ├── writingPattern.ts # ✅ 패턴 분석 타입
|
||||
│ ├── writingSession.ts # 🆕 실시간 모니터링 타입 (WritingStats, PreviewRequest/Response)
|
||||
│ ├── api/ # ✅ API Request/Response 타입
|
||||
│ │ ├── topic.ts # ✅ 주제 API (팀/개인 주제)
|
||||
│ │ ├── team.ts # ✅ 팀 API
|
||||
│ │ └── student.ts # ✅ 학생 API
|
||||
│ │ ├── user.ts # ✅ 사용자 API
|
||||
│ │ └── writing.ts # ✅ 글쓰기 API
|
||||
│ └── [미구현]
|
||||
│
|
||||
└── utils/ # 유틸리티 함수
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 라온누리 - 개발 로드맵
|
||||
|
||||
> 최종 업데이트: 2025-11-12 (Writing API, 패턴 분석 확장, Content Hash 캐싱)
|
||||
> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링 시스템)
|
||||
|
||||
초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획
|
||||
|
||||
@ -83,6 +83,7 @@
|
||||
| **Writing API 구현** | **POST /api/writing (글 생성), GET/PUT/DELETE /api/writing/[id] (조회/수정/삭제), POST /api/writing/user (목록), POST /api/writing/recent (최근 글), src/lib/server/writing.ts (Firestore CRUD), 권한 체크 (작성자만), 텍스트 통계 자동 계산** | **2025-11-12** |
|
||||
| **패턴 분석 - 팀 소유자 기능** | **3가지 분석 타입 (self/by-team/by-topic), API 권한 체크, 팀 주제 필터링, WritingPatternDialog Props 확장, TopicMemberAnalysisSection (UI만), 팀 관리 페이지에 멤버 분석 메뉴** | **2025-11-12** |
|
||||
| **Content Hash 기반 3단계 캐싱** | **글 목록 해시 생성 (id+updatedAt), L1: localStorage (영구, LRU 10개), L2: Firestore patternAnalyses (영구), L3: Server in-memory (5분, 50개), 변경 자동 감지, AI 비용 절감 (전체 사용자 기준 1회 분석), contentHash.ts + patternCacheManager.ts + patternAnalysis.ts** | **2025-11-12** |
|
||||
| **실시간 글쓰기 모니터링** | **Firebase Realtime Database 기반 실시간 통신 (Redis Pub/Sub 방식), WritingSessionManager (5초 주기 통계 전송, 미리보기 요청-응답, 상세 디버그 로그), LiveWritingMonitor 컴포넌트 (주제 Select, 실시간 카드 그리드, StudentMonitorCard), 글쓰기 페이지 모니터링 전송 (contentRef로 최적화), onDisconnect() 자동 정리, 작성 속도 계산 (클라이언트 측, 글자/분), Sparkline 그래프 (Area Chart, 최근 10개 히스토리, 0도 표시), 인터랙티브 툴팁 (속도 + 몇 초 전 데이터), 마지막 업데이트 시간 표시, database.rules.json (topicId 레벨 읽기 권한), @chakra-ui/charts + recharts 패키지, 완전 무료 (100명까지)** | **2025-11-12** |
|
||||
|
||||
### 🚧 진행 중
|
||||
|
||||
|
||||
211
TECH_STACK.md
211
TECH_STACK.md
@ -1,6 +1,6 @@
|
||||
# 라온누리 - 기술 스택 및 개발 환경
|
||||
|
||||
> 최종 업데이트: 2025-11-11 (글 작성 패턴 분석, 실시간 하이라이트, 인터랙티브 툴팁)
|
||||
> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링 시스템)
|
||||
|
||||
---
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
| 기술 | 버전 | 용도 |
|
||||
|-----|------|------|
|
||||
| **Chakra UI** | v3.28.0 | 컴포넌트 라이브러리 |
|
||||
| **@chakra-ui/charts** | latest | 🆕 **차트 컴포넌트** (Sparkline, Area/Bar/Line 차트) |
|
||||
| **Recharts** | latest | 🆕 **차트 라이브러리** (Chakra Charts 내부 사용) |
|
||||
| **Emotion** | 11.14.0 | CSS-in-JS |
|
||||
| **Framer Motion** | 12.23.24 | 애니메이션 라이브러리 |
|
||||
| **React Icons** | 5.5.0 | 아이콘 세트 |
|
||||
@ -31,7 +33,8 @@
|
||||
| **Firebase** | 12.4.0 | BaaS (Backend as a Service) |
|
||||
| **Firebase Auth** | - | 사용자 인증 |
|
||||
| **Firestore** | - | NoSQL 데이터베이스 (글 저장) |
|
||||
| **@google/genai** | 1.29.0 | 🆕 **Google Gemini API SDK** (텍스트 분석, 맞춤법 검사) |
|
||||
| **Firebase Realtime Database** | - | 🆕 **실시간 데이터 동기화** (글쓰기 모니터링) |
|
||||
| **@google/genai** | 1.29.0 | Google Gemini API SDK (텍스트 분석, 맞춤법 검사) |
|
||||
| **Redis** | - | Cache 데이터 베이스 (예정) |
|
||||
|
||||
**AI 서비스**:
|
||||
@ -45,7 +48,14 @@
|
||||
|
||||
| 기술 | 버전 | 용도 |
|
||||
|-----|------|------|
|
||||
| **use-debounce** | latest | 🆕 React debounce hook (5초 API 호출 제한) |
|
||||
| **use-debounce** | latest | React debounce hook (5초 API 호출 제한) |
|
||||
|
||||
### Charts
|
||||
|
||||
| 기술 | 버전 | 용도 |
|
||||
|-----|------|------|
|
||||
| **@chakra-ui/charts** | latest | 🆕 **Chakra UI 차트 컴포넌트** (실시간 모니터링 그래프) |
|
||||
| **recharts** | latest | 🆕 **차트 라이브러리** (Area, Line, Bar 차트) |
|
||||
|
||||
### State Management
|
||||
|
||||
@ -118,19 +128,37 @@ import { Navbar } from "@/components/navigation/Navbar";
|
||||
|
||||
---
|
||||
|
||||
## 환경 변수
|
||||
## Firebase 설정
|
||||
|
||||
### `.env.local` 파일 구조
|
||||
### 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); // 🆕
|
||||
```
|
||||
|
||||
**보안 참고**:
|
||||
- Public API Key는 클라이언트 SDK 표준 방식 (Firebase 프로젝트 설정에서 도메인 제한)
|
||||
- 환경변수 대신 코드에 포함 (일반적인 Firebase 클라이언트 앱 패턴)
|
||||
|
||||
### 환경 변수
|
||||
|
||||
```bash
|
||||
# Firebase 설정 (필수)
|
||||
NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
|
||||
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
|
||||
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id
|
||||
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
|
||||
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
|
||||
NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
|
||||
|
||||
# 사이트 URL (프로덕션)
|
||||
NEXT_PUBLIC_URL=https://raonnuri.com
|
||||
|
||||
@ -138,17 +166,6 @@ NEXT_PUBLIC_URL=https://raonnuri.com
|
||||
NEXT_PUBLIC_API_URL=/api
|
||||
```
|
||||
|
||||
### 환경 변수 사용 예시
|
||||
|
||||
```typescript
|
||||
// src/config/firebase.ts
|
||||
const firebaseConfig = {
|
||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Firebase 설정
|
||||
@ -467,14 +484,156 @@ const nickname = teamManager.getMemberNickname(team, uid, user?.name);
|
||||
└─> migrateLegacyDraft() - 기존 단일 draft 마이그레이션
|
||||
```
|
||||
|
||||
### 4. 상태 관리 원칙
|
||||
### 4. 실시간 글쓰기 모니터링 아키텍처 (Firebase Realtime Database)
|
||||
|
||||
```
|
||||
관리자 (팀 관리 페이지)
|
||||
↓ 주제 선택
|
||||
↓
|
||||
LiveWritingMonitor 컴포넌트
|
||||
├─> 주제 드롭다운 (teamTopics)
|
||||
└─> subscribeToTopic(teamId, topicId, callback)
|
||||
↓ Firebase Realtime DB 구독
|
||||
monitoring/{teamId}/{topicId}/{userId}
|
||||
↑ 5초마다 업데이트
|
||||
학생 (글쓰기 페이지)
|
||||
├─> 팀 주제 선택 감지
|
||||
└─> startMonitoring(teamId, topicId, getStats)
|
||||
├─> 5초마다 통계 전송 (contentLength, wordCount)
|
||||
├─> onDisconnect().remove() 설정
|
||||
└─> 페이지 이탈 시 자동 정리
|
||||
|
||||
미리보기 요청-응답 플로우
|
||||
관리자: requestPreview(userId)
|
||||
↓ 요청 생성
|
||||
previewRequests/{userId}/{requestId}
|
||||
↓ 학생 리스너 감지
|
||||
학생: listenForPreviewRequests(callback)
|
||||
↓ 현재 글 내용 전송
|
||||
previewResponses/{requestId}
|
||||
↓ 관리자 구독
|
||||
관리자: Promise 해결 → Dialog 표시
|
||||
```
|
||||
|
||||
**주요 구성 요소**:
|
||||
- **WritingSessionManager** (`src/managers/WritingSessionManager.ts`):
|
||||
- `startMonitoring(teamId, topicId, getStatsCallback)`: 5초 주기 통계 전송
|
||||
- `stopMonitoring()`: 전송 중지 + DB 삭제
|
||||
- `subscribeToTopic(teamId, topicId, callback)`: 실시간 구독 (관리자)
|
||||
- `requestPreview(userId)`: 미리보기 요청 (Promise 반환)
|
||||
- `listenForPreviewRequests(onRequestCallback)`: 미리보기 리스너 (학생)
|
||||
- 상세 디버그 로그 (전송/수신/에러 추적)
|
||||
|
||||
- **LiveWritingMonitor** (`src/components/team/LiveWritingMonitor.tsx`):
|
||||
- 주제 선택 Select 컴포넌트 (Chakra UI Select)
|
||||
- 실시간 학생 카드 그리드 (StudentMonitorCard)
|
||||
- 유저 정보와 통계 자동 결합 (userManager 활용)
|
||||
- **작성 속도 실시간 계산** (클라이언트 측, 글자/분)
|
||||
- **Sparkline 그래프** (Area Chart, 최근 10개 히스토리)
|
||||
- **인터랙티브 툴팁** (속도 값 + 몇 초 전 데이터)
|
||||
- 미리보기 Dialog
|
||||
- 마지막 업데이트 시간 표시 ("N초 전")
|
||||
|
||||
**Realtime DB 구조**:
|
||||
```json
|
||||
{
|
||||
"monitoring": {
|
||||
"{teamId}": {
|
||||
"{topicId}": {
|
||||
"{userId}": {
|
||||
"userId": "abc123",
|
||||
"contentLength": 1500,
|
||||
"wordCount": 300,
|
||||
"topicId": "topic_123",
|
||||
"lastUpdated": 1731400800000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"previewRequests": {
|
||||
"{userId}": {
|
||||
"{requestId}": {
|
||||
"requestedBy": "admin_uid",
|
||||
"timestamp": 1234567890,
|
||||
"requestId": "req_xyz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"previewResponses": {
|
||||
"{requestId}": {
|
||||
"content": "현재 작성 중인 글...",
|
||||
"timestamp": 1234567890,
|
||||
"requestId": "req_xyz"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Security Rules** (`database.rules.json`):
|
||||
```json
|
||||
{
|
||||
"rules": {
|
||||
"monitoring": {
|
||||
"$teamId": {
|
||||
"$topicId": {
|
||||
".read": "auth != null",
|
||||
"$userId": {
|
||||
".write": "auth != null && auth.uid == $userId"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"previewRequests": {
|
||||
"$userId": {
|
||||
".read": "auth != null && auth.uid == $userId",
|
||||
".write": "auth != null"
|
||||
}
|
||||
},
|
||||
"previewResponses": {
|
||||
"$requestId": {
|
||||
".read": "auth != null",
|
||||
".write": "auth != null"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**권한 정책**:
|
||||
- 통계 읽기: 인증된 모든 사용자 (팀 소유자만 UI 접근 가능)
|
||||
- 통계 쓰기: 본인만
|
||||
- 미리보기: 요청자와 대상자만
|
||||
|
||||
**작성 속도 계산 로직** (클라이언트 측):
|
||||
```typescript
|
||||
// 5초마다 데이터 수신
|
||||
const charDiff = 현재글자수 - 이전글자수;
|
||||
const speed = charDiff * 12; // 5초 * 12 = 60초(1분)
|
||||
|
||||
// 히스토리 저장 (최근 10개)
|
||||
speedHistory.push({ speed, timestamp: Date.now() });
|
||||
if (speedHistory.length > 10) speedHistory.shift();
|
||||
|
||||
// Sparkline 그래프로 시각화
|
||||
- Area Chart (면적 그래프)
|
||||
- Teal 색상, 투명도 30%
|
||||
- 툴팁: 마우스 오버 시 "N자/분" + "N초 전" 표시
|
||||
- 0도 표시 (작성 멈춤 시각화)
|
||||
```
|
||||
|
||||
**비용 효율성**:
|
||||
- Firebase Realtime DB Spark 플랫폼: 동시 접속 100명까지 **완전 무료**
|
||||
- GB 다운로드 기반 과금 (쓰기/읽기 횟수 무관)
|
||||
- 30명 × 1시간 수업 = ~1.5MB (무료 한도 1GB/day의 0.15%)
|
||||
|
||||
### 5. 상태 관리 원칙
|
||||
|
||||
- **전역 상태**: Zustand 사용 (인증, 사용자 진행 상황, 알림)
|
||||
- **로컬 상태**: `useState` 사용 (폼 입력, UI 토글, 에디터 내용)
|
||||
- **로컬 저장소**: LocalStorage (임시 저장 글)
|
||||
- **서버 상태**: Firestore 직접 호출 (React Query는 나중에 고려)
|
||||
|
||||
### 5. 태그 입력 필드 패턴 (Tag Input Field)
|
||||
### 6. 태그 입력 필드 패턴 (Tag Input Field)
|
||||
|
||||
CreateTopicDialog의 제목 템플릿 입력에 사용되는 고급 UI 패턴입니다.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user