# Firestore 컬렉션 구조 설계

> **작성일**: 2026-02-08 20:59
> **작성자**: Data Agent, Backend Agent
> **상태**: ✅ 검토 완료, 수정됨

---

## 1. 컬렉션 구조 개요

```
🔥 Firestore
│
├── 📁 users/                    ← 사용자
│   └── {userId}/
│       ├── email, name, role, ...
│       └── 📁 apiKeys/          ← BYOK API 키 (서브컬렉션)
│           └── {service}/       ← gemini, whisper
│
├── 📁 documents/                ← 문서
│   └── {docId}/
│       ├── title, content, visibility, ...
│       ├── 📁 versions/         ← 버전 히스토리 (서브컬렉션)
│       │   └── {versionId}/
│       └── 📁 tags/             ← 태그 (서브컬렉션)
│           └── {tagId}/
│
├── 📁 links/                    ← 백링크 (루트 컬렉션)
│   └── {linkId}/
│
├── 📁 dailyNotes/               ← Daily Notes
│   └── {noteId}/                ← 수정: 복합 ID → 별도 필드
│
├── 📁 drafts/                   ← Quick Capture 임시 저장 (추가)
│   └── {draftId}/
│
└── 📁 whitelist/                ← 접근 허용 이메일
    └── {email}/
```

---

## 2. 컬렉션 상세 정의

### 2.1 users (사용자)

```typescript
// /users/{userId}
{
  id: string,              // Firebase Auth UID
  email: string,           // Google OAuth 이메일
  name: string,            // 표시 이름
  photoURL?: string,       // 프로필 사진
  role: 'admin' | 'editor' | 'viewer',
  createdAt: Timestamp,
  lastLoginAt: Timestamp
}
```

#### 서브컬렉션: apiKeys (BYOK)
```typescript
// /users/{userId}/apiKeys/{service}
{
  encryptedKey: string,    // AES-256 암호화된 API 키
  service: 'gemini' | 'whisper',
  updatedAt: Timestamp
}
```

**🔐 암호화 키 관리** (QA 피드백 반영)
- 암호화 키: Firebase Secret Manager 또는 Cloud Functions 환경변수
- 키 이름: `ENCRYPTION_KEY`
- 복호화: 서버 사이드(Cloud Functions)에서만 수행

---

### 2.2 documents (문서)

```typescript
// /documents/{docId}
{
  id: string,
  title: string,
  content: string,         // 마크다운 본문
  visibility: 'private' | 'shared',
  authorId: string,
  authorName: string,      // 비정규화
  
  // 메타데이터 (Obsidian Properties)
  properties: {
    status?: 'draft' | 'review' | 'published',
    category?: string,
    priority?: 'low' | 'medium' | 'high',
    assignee?: string,
    dueDate?: Timestamp
  },
  
  // 검색용 - Cloud Functions로 자동 생성 (Data 피드백 반영)
  searchKeywords: string[],
  
  // 타임스탬프
  createdAt: Timestamp,
  updatedAt: Timestamp,
  
  // 동시 편집용 (Top 2에서 상세화)
  editingBy?: string,
  editingAt?: Timestamp
}
```

**🔄 키워드 자동 추출** (Data 피드백 반영)
```typescript
// Cloud Functions - onWrite 트리거
exports.extractKeywords = functions.firestore
  .document('documents/{docId}')
  .onWrite(async (change, context) => {
    const doc = change.after.data();
    const keywords = extractKeywordsFromContent(doc.title, doc.content);
    await change.after.ref.update({ searchKeywords: keywords });
  });
```

---

### 2.3 dailyNotes (Daily Notes) - 수정됨

```typescript
// /dailyNotes/{noteId}  ← Backend 피드백 반영: ID 형식 변경
{
  id: string,              // auto-generated
  userId: string,          // 별도 필드로 분리
  date: string,            // "2026-02-08" - 별도 필드
  content: string,
  createdAt: Timestamp,
  updatedAt: Timestamp
}
```

**인덱스**: `userId` + `date` (복합 인덱스)

---

### 2.4 drafts (Quick Capture) - 추가됨

```typescript
// /drafts/{draftId}  ← Reflect 피드백 반영
{
  id: string,
  userId: string,
  content: string,         // 빠른 메모 내용
  convertedToDocId?: string,  // 문서로 변환 시 연결
  createdAt: Timestamp
}
```

**자동 정리**: 30일 후 미변환 draft 자동 삭제 (Cloud Functions 스케줄러)

---

### 2.5 links (백링크)

```typescript
// /links/{linkId}
{
  id: string,
  sourceDocId: string,
  sourceTitle: string,     // 비정규화
  targetDocId: string,
  targetTitle: string,     // 비정규화
  createdAt: Timestamp
}
```

---

### 2.6 whitelist (접근 제어)

```typescript
// /whitelist/{email}
{
  email: string,
  addedBy: string,
  addedAt: Timestamp,
  role: 'admin' | 'editor' | 'viewer'
}
```

---

## 3. 인덱스 전략

### 복합 인덱스
| 컬렉션 | 필드 | 용도 |
|--------|------|------|
| documents | `visibility`, `createdAt` | 공유 문서 최신순 |
| documents | `authorId`, `visibility` | 내 문서 필터 |
| dailyNotes | `userId`, `date` | 사용자별 날짜 조회 |
| links | `targetDocId`, `createdAt` | 백링크 조회 |
| drafts | `userId`, `createdAt` | 사용자별 임시글 |

---

## 4. 보안 규칙 (Firestore Rules)

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    function isWhitelisted() {
      return exists(/databases/$(database)/documents/whitelist/$(request.auth.token.email));
    }
    
    function isAdmin() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }
    
    // 사용자
    match /users/{userId} {
      allow read: if isWhitelisted();
      allow write: if request.auth.uid == userId;
      
      match /apiKeys/{service} {
        allow read, write: if request.auth.uid == userId;
      }
    }
    
    // 문서
    match /documents/{docId} {
      allow read: if isWhitelisted() && 
        (resource.data.visibility == 'shared' || 
         resource.data.authorId == request.auth.uid);
      allow create: if isWhitelisted();
      allow update, delete: if resource.data.authorId == request.auth.uid || isAdmin();
      
      match /versions/{versionId} {
        allow read, write: if isWhitelisted();
      }
      match /tags/{tagId} {
        allow read, write: if isWhitelisted();
      }
    }
    
    // 링크
    match /links/{linkId} {
      allow read, write: if isWhitelisted();
    }
    
    // Daily Notes (본인만)
    match /dailyNotes/{noteId} {
      allow read, write: if resource.data.userId == request.auth.uid;
    }
    
    // Drafts (본인만)
    match /drafts/{draftId} {
      allow read, write: if resource.data.userId == request.auth.uid;
    }
    
    // 화이트리스트 (관리자만)
    match /whitelist/{email} {
      allow read: if isWhitelisted();
      allow write: if isAdmin();
    }
  }
}
```

---

## 5. 에이전트 검토 결과 (실질적 검토)

### Data Agent
❓ 문제: searchKeywords 배열이 수동 관리됨
→ 키워드 추출 로직이 없으면 검색 누락 발생

💡 개선: Cloud Functions onWrite 트리거로 자동 추출 → **반영됨 ✅**

---

### Backend Agent
❓ 문제: dailyNotes ID가 "{userId}_{YYYYMMDD}" 복합 형식
→ 쿼리 시 문자열 파싱 필요

💡 개선: userId, date를 별도 필드로 분리 → **반영됨 ✅**

---

### QA Agent
❓ 문제: API 키 암호화 키 관리 방법 미정의
→ encryptedKey는 있지만 암호화/복호화 키 저장 위치 불명확

💡 개선: Firebase Secret Manager 또는 환경변수 정의 → **반영됨 ✅**

---

### Reflect Agent
❓ 문제: Quick Capture 임시 저장소 없음
→ 빠른 메모 후 정리 안 된 상태의 저장 위치 미정의

💡 개선: drafts/ 컬렉션 추가 → **반영됨 ✅**

---

## 6. 다음 단계

**Top 2**: 동시 편집 전략 (`editingBy`, `editingAt` 필드 상세화)
