feat(V2-2): Supabase 인프라 — 스키마 + RLS + Edge Functions 스캐폴딩
server/supabase/ 신규 디렉토리:
config.toml — Supabase CLI 설정
- 프로젝트 ID, DB 포트, auth providers (Google/GitHub/Apple),
edge_runtime, functions.*.verify_jwt 설정
migrations/ (PostgreSQL DDL, 시간순):
- 20260409000001_initial_schema.sql
12개 테이블: profiles, teams, team_members, meetings,
meeting_memos, meeting_documents, transcripts, history,
dictionary, memo_tags, daily_usage, subscriptions
+ 인덱스 + FK CASCADE + transcripts Realtime publication
- 20260409000002_rls_policies.sql
개인 전용(history/dictionary): user_id = auth.uid()
팀 공유(meetings 등): 본인 OR team_members 조회 subquery
daily_usage: 읽기만, 쓰기는 service_role RPC
- 20260409000003_auth_triggers.sql
handle_new_user — auth.users INSERT → profiles+subscriptions 자동 생성
moddatetime — 8개 테이블 updated_at 자동 갱신
increment_daily_usage — service_role 전용 쿼터 RPC
- 20260409000004_storage_buckets.sql
audio/exports/avatars 3개 버킷 + 경로 기반 접근 정책
(파일 경로가 {user_id}/...로 시작해야 쓰기 허용)
functions/ (Deno/TypeScript Edge Functions):
- _shared/cors.ts — CORS 헤더 + preflight 핸들러
- _shared/auth.ts — requireUser (JWT 검증 + User 반환)
- _shared/quota.ts — 티어별 쿼터 체크 + consume + service role client
- stt-proxy/index.ts — Google Cloud STT 래퍼 스캐폴딩
placeholder 응답, 실제 API 호출 코드는 주석으로 포함
- llm-proxy/index.ts — Anthropic Messages API 래퍼 스캐폴딩
티어별 허용 모델 정책 (free=Haiku, pro=Sonnet, team=Opus)
설계 문서:
- docs/v2/phase-V2-2.md — 상세 설계 (스키마/RLS/Edge Functions/Realtime)
- docs/v2/phase-V2-2-setup.md — 사용자 액션 가이드 (Supabase 계정/
OAuth 등록/CLI/배포/검증)
apps/desktop typecheck 통과 (server/는 Deno 런타임이라 별도).
실제 Supabase 프로젝트 배포는 사용자가 phase-V2-2-setup.md 따라 수행.
This commit is contained in:
parent
3524e958ba
commit
97eb886ec3
16 changed files with 1675 additions and 8 deletions
184
docs/v2/phase-V2-2-setup.md
Normal file
184
docs/v2/phase-V2-2-setup.md
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# Phase V2-2: Supabase 인프라 — 사용자 설정 가이드
|
||||
|
||||
> 이 문서는 사용자가 **로컬 밖에서 수행해야 하는 작업**만 안내합니다.
|
||||
> 로컬 코드 변경(SQL 마이그레이션, Edge Functions 스캐폴딩)은 이미 `server/supabase/`에 포함되어 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. Supabase 계정/프로젝트 생성
|
||||
|
||||
1. https://supabase.com 접속 → `Sign in with GitHub` (권장)
|
||||
2. `New project` 클릭
|
||||
- **Name**: `d3ro-voice`
|
||||
- **Database Password**: 강력한 비밀번호 (1Password 등에 저장)
|
||||
- **Region**: `ap-northeast-2` (Seoul) 권장
|
||||
- **Pricing Plan**: Free (시작 후 Pro로 업그레이드 가능)
|
||||
3. 프로젝트 생성 완료(~2분) 후 대시보드 접속
|
||||
|
||||
## 2. API 키 확인 (Settings → API)
|
||||
|
||||
다음 4개 값을 메모:
|
||||
|
||||
| 키 | 용도 | 노출 범위 |
|
||||
|---|---|---|
|
||||
| `Project URL` | `SUPABASE_URL` — 모든 클라이언트 | 공개 가능 |
|
||||
| `anon public` | `SUPABASE_ANON_KEY` — 클라이언트 읽기/쓰기 (RLS 보호) | 공개 가능 |
|
||||
| `service_role` | `SUPABASE_SERVICE_ROLE_KEY` — 서버 전용 (RLS 우회) | **절대 공개 금지** |
|
||||
| `Project Ref` | CLI 연동용 | 공개 가능 |
|
||||
|
||||
## 3. Supabase CLI 설치
|
||||
|
||||
### Windows
|
||||
```bash
|
||||
scoop install supabase
|
||||
# 또는
|
||||
winget install Supabase.CLI
|
||||
```
|
||||
|
||||
### macOS
|
||||
```bash
|
||||
brew install supabase/tap/supabase
|
||||
```
|
||||
|
||||
## 4. 로컬 프로젝트 연결
|
||||
|
||||
```bash
|
||||
cd D:/workspace/voice
|
||||
|
||||
# Supabase 계정 로그인 (브라우저 열림)
|
||||
supabase login
|
||||
|
||||
# 원격 프로젝트와 연결 (Project Ref 필요)
|
||||
cd server/supabase
|
||||
supabase link --project-ref <YOUR_PROJECT_REF>
|
||||
```
|
||||
|
||||
## 5. 마이그레이션 푸시
|
||||
|
||||
```bash
|
||||
# server/supabase/migrations/* 를 원격에 적용
|
||||
supabase db push
|
||||
```
|
||||
|
||||
실행 결과:
|
||||
- `20260409000001_initial_schema.sql` — 테이블/인덱스 생성
|
||||
- `20260409000002_rls_policies.sql` — RLS 정책
|
||||
- `20260409000003_auth_triggers.sql` — handle_new_user, moddatetime
|
||||
- `20260409000004_storage_buckets.sql` — storage 버킷 + 정책
|
||||
|
||||
검증: Studio → Database → Tables에서 12개 테이블 확인
|
||||
|
||||
## 6. OAuth Provider 등록
|
||||
|
||||
### 6.1 Google OAuth
|
||||
|
||||
1. https://console.cloud.google.com → 프로젝트 생성 (`d3ro-voice`)
|
||||
2. `APIs & Services` → `Credentials` → `Create Credentials` → `OAuth client ID`
|
||||
3. `Application type`: Web application
|
||||
4. `Authorized redirect URIs`:
|
||||
- `https://<YOUR_PROJECT_REF>.supabase.co/auth/v1/callback`
|
||||
5. `Client ID`와 `Client Secret` 복사
|
||||
6. Supabase 대시보드 → Authentication → Providers → Google
|
||||
- Enable 체크
|
||||
- Client ID / Client Secret 입력 → Save
|
||||
|
||||
### 6.2 GitHub OAuth
|
||||
|
||||
1. GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
|
||||
2. `Homepage URL`: `https://d3ro.dev`
|
||||
3. `Authorization callback URL`: `https://<YOUR_PROJECT_REF>.supabase.co/auth/v1/callback`
|
||||
4. `Client ID`, `Client Secret` 복사 → Supabase Authentication → Providers → GitHub
|
||||
|
||||
### 6.3 Apple OAuth (선택, 나중에 가능)
|
||||
|
||||
Apple Developer Program 계정 필요. iOS 출시 전에 등록.
|
||||
가이드: https://supabase.com/docs/guides/auth/social-login/auth-apple
|
||||
|
||||
## 7. Edge Functions 배포
|
||||
|
||||
```bash
|
||||
cd server/supabase
|
||||
|
||||
# 시크릿 설정 (배포 전)
|
||||
supabase secrets set GOOGLE_CLOUD_STT_KEY=<google_api_key>
|
||||
supabase secrets set ANTHROPIC_API_KEY=<anthropic_api_key>
|
||||
|
||||
# 함수 배포
|
||||
supabase functions deploy stt-proxy
|
||||
supabase functions deploy llm-proxy
|
||||
```
|
||||
|
||||
배포 후 호출 URL:
|
||||
- `https://<YOUR_PROJECT_REF>.supabase.co/functions/v1/stt-proxy`
|
||||
- `https://<YOUR_PROJECT_REF>.supabase.co/functions/v1/llm-proxy`
|
||||
|
||||
### 7.1 AI API 키 발급
|
||||
|
||||
**Google Cloud STT**:
|
||||
1. Google Cloud Console → API & Services → Library → Speech-to-Text API → Enable
|
||||
2. Credentials → Create API Key (Speech-to-Text API로 제한 권장)
|
||||
3. 빌링 활성화 필요 ($300 free credit)
|
||||
|
||||
**Anthropic API**:
|
||||
1. https://console.anthropic.com → API Keys → Create Key
|
||||
2. 최소 $5 credit 충전
|
||||
|
||||
## 8. 환경변수 관리
|
||||
|
||||
### 8.1 로컬 개발 (.env)
|
||||
|
||||
루트에 `.env.local` 생성 (`.gitignore`되어 있음):
|
||||
|
||||
```bash
|
||||
# Supabase
|
||||
VITE_SUPABASE_URL=https://<YOUR_PROJECT_REF>.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=<anon_public_key>
|
||||
|
||||
# apps/web (Phase V2-3에서 사용)
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://<YOUR_PROJECT_REF>.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon_public_key>
|
||||
|
||||
# Edge Functions 로컬 개발용 (server/supabase/.env)
|
||||
GOOGLE_CLOUD_STT_KEY=<google_api_key>
|
||||
ANTHROPIC_API_KEY=<anthropic_api_key>
|
||||
```
|
||||
|
||||
### 8.2 프로덕션 (Supabase Dashboard)
|
||||
|
||||
- Supabase 내부 시크릿: `supabase secrets set KEY=value`
|
||||
- Vercel/Netlify 등 호스팅: 각 플랫폼의 환경변수 설정 페이지 사용
|
||||
|
||||
## 9. 검증 체크리스트
|
||||
|
||||
- [ ] Studio에서 12개 테이블 확인 (profiles, teams, team_members, meetings, meeting_memos, meeting_documents, transcripts, history, dictionary, memo_tags, daily_usage, subscriptions)
|
||||
- [ ] Authentication → Users에서 첫 테스트 유저 로그인 가능
|
||||
- [ ] 테스트 유저 로그인 후 `profiles` 테이블에 자동 행 생성됨 (handle_new_user 트리거 동작)
|
||||
- [ ] `subscriptions` 테이블에도 `tier=free` 행 자동 생성
|
||||
- [ ] Storage → 3개 버킷 (audio/exports/avatars) 확인
|
||||
- [ ] Edge Functions → stt-proxy, llm-proxy 배포 상태 확인
|
||||
- [ ] Edge Functions 호출 테스트 (curl 또는 Studio의 Invoke):
|
||||
```bash
|
||||
curl -X POST https://<YOUR_PROJECT_REF>.supabase.co/functions/v1/llm-proxy \
|
||||
-H "Authorization: Bearer <user_jwt>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"messages":[{"role":"user","content":"hi"}]}'
|
||||
```
|
||||
→ `"[llm-proxy placeholder ...]"` 응답이 오면 인증/쿼터 체인 정상
|
||||
|
||||
## 10. 다음 페이즈와의 연결
|
||||
|
||||
V2-2가 완료되면:
|
||||
- **V2-3 (Web MVP)**: `NEXT_PUBLIC_SUPABASE_*` 환경변수로 Next.js 앱이 백엔드 연결
|
||||
- **V2-4 (데스크톱 동기화)**: `VITE_SUPABASE_*`로 Electron 앱이 백엔드 연결
|
||||
- Edge Functions의 placeholder를 실제 Google STT / Anthropic API 호출로 교체
|
||||
|
||||
## 11. 문제 해결
|
||||
|
||||
**`supabase db push` 시 FK 오류**:
|
||||
→ 기존 스키마가 있으면 `supabase db reset` (주의: 데이터 삭제) 또는 수동 drop 후 재시도
|
||||
|
||||
**RLS로 인해 INSERT 차단**:
|
||||
→ `auth.uid()`가 null이면 정책 통과 못함. 반드시 로그인된 클라이언트에서 호출
|
||||
|
||||
**Edge Function 배포 후 500 에러**:
|
||||
→ `supabase functions logs stt-proxy`로 런타임 에러 확인. 대부분 시크릿 미설정
|
||||
263
docs/v2/phase-V2-2.md
Normal file
263
docs/v2/phase-V2-2.md
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# Phase V2-2: Supabase 인프라 — 상세 설계
|
||||
|
||||
> 멀티플랫폼(desktop/web/mobile)의 **단일 백엔드**를 Supabase 기반으로 구축.
|
||||
> 로컬 완결 작업(SQL 마이그레이션, Edge Functions)은 이 페이즈에서 전부 작성.
|
||||
> 사용자 액션(계정 생성, OAuth 등록, 배포)은 `phase-V2-2-setup.md` 참조.
|
||||
|
||||
---
|
||||
|
||||
## 1. 목표
|
||||
|
||||
1. **인증**: Supabase Auth — Google/GitHub/Apple OAuth + Email
|
||||
2. **데이터 SSOT**: PostgreSQL — V1 SQLite 스키마를 멀티테넌시(user_id, team_id)로 포팅
|
||||
3. **보안**: Row Level Security — 개인 데이터 격리 + 팀 공유 지원
|
||||
4. **AI 프록시**: Edge Functions — Google STT, Claude LLM 호출을 서버에서 래핑 (API 키 은닉 + 쿼터 집계)
|
||||
5. **파일 스토리지**: Supabase Storage 버킷 3개 (audio, exports, avatars)
|
||||
6. **실시간**: 회의 진행 중 전사 세그먼트 실시간 동기화 (Realtime subscription)
|
||||
|
||||
## 2. 디렉토리 구조
|
||||
|
||||
```
|
||||
server/supabase/
|
||||
├── config.toml # 프로젝트 설정 (로컬 개발용)
|
||||
├── seed.sql # 개발 시드 데이터
|
||||
├── migrations/
|
||||
│ ├── 20260409000001_initial_schema.sql # 테이블 + 인덱스
|
||||
│ ├── 20260409000002_rls_policies.sql # RLS policies
|
||||
│ ├── 20260409000003_auth_triggers.sql # handle_new_user, moddatetime
|
||||
│ └── 20260409000004_storage_buckets.sql # storage 버킷 + policies
|
||||
└── functions/
|
||||
├── _shared/
|
||||
│ ├── cors.ts # CORS 헤더 헬퍼
|
||||
│ ├── auth.ts # JWT 검증 + user_id 추출
|
||||
│ └── quota.ts # 티어별 쿼터 체크
|
||||
├── stt-proxy/
|
||||
│ └── index.ts # Google Cloud STT 래퍼
|
||||
└── llm-proxy/
|
||||
└── index.ts # Claude API 래퍼
|
||||
```
|
||||
|
||||
## 3. 데이터 모델
|
||||
|
||||
### 3.1 테이블 요약
|
||||
|
||||
| 테이블 | 설명 | 멀티테넌시 |
|
||||
|---|---|---|
|
||||
| `public.profiles` | auth.users 확장 (name, avatar_url, locale, tier) | `id = auth.users.id` |
|
||||
| `public.meetings` | 회의 세션 (V1 meeting_sessions) | `user_id`, `team_id?` |
|
||||
| `public.meeting_memos` | 회의 중 타임스탬프 메모 | `meeting_id → meetings` |
|
||||
| `public.meeting_documents` | LLM 생성 문서 (minutes/report/mindmap) | `meeting_id → meetings` |
|
||||
| `public.transcripts` | 전사 세그먼트 (실시간 동기화 대상) | `meeting_id → meetings` |
|
||||
| `public.history` | 음성 입력 히스토리 (dictation/translate 등) | `user_id` |
|
||||
| `public.dictionary` | 사용자 사전 | `user_id` |
|
||||
| `public.memo_tags` | 히스토리 태그 | `user_id`, `history_id → history` |
|
||||
| `public.daily_usage` | 기능별 일일 사용량 (쿼터 집계) | `user_id` |
|
||||
| `public.teams` | 팀 | `owner_id → auth.users` |
|
||||
| `public.team_members` | 팀 멤버십 | `team_id`, `user_id`, role |
|
||||
| `public.subscriptions` | Stripe 구독 상태 | `user_id` |
|
||||
|
||||
### 3.2 V1 SQLite ↔ V2 PostgreSQL 타입 매핑
|
||||
|
||||
| V1 (drizzle sqlite-core) | V2 (PostgreSQL) | 비고 |
|
||||
|---|---|---|
|
||||
| `text('id').primaryKey()` (nanoid) | `uuid primary key default gen_random_uuid()` | 신규는 UUID 사용 |
|
||||
| `integer('created_at')` (epoch ms) | `timestamptz not null default now()` | Date/Time 네이티브 |
|
||||
| `integer('indexed', mode: 'boolean')` | `boolean not null default false` | |
|
||||
| `real('duration')` | `double precision` | |
|
||||
| `text('status', {enum: ...})` | `text check (status in (...))` 또는 enum | enum 타입으로 |
|
||||
| `text('embedding')` (JSON float[]) | `vector(768)` via pgvector | RAG 전용 |
|
||||
|
||||
### 3.3 공통 컬럼
|
||||
|
||||
모든 유저 소유 테이블:
|
||||
- `user_id uuid not null references auth.users(id) on delete cascade`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()` (트리거로 자동 갱신)
|
||||
|
||||
회의 관련 테이블만 추가:
|
||||
- `team_id uuid references public.teams(id) on delete set null` (nullable — 개인 회의는 null)
|
||||
|
||||
## 4. RLS 정책 패턴
|
||||
|
||||
### 4.1 개인 전용 테이블 (history, dictionary, memo_tags, daily_usage)
|
||||
|
||||
```sql
|
||||
CREATE POLICY "users_own_rows" ON public.history
|
||||
FOR ALL
|
||||
USING (user_id = auth.uid())
|
||||
WITH CHECK (user_id = auth.uid());
|
||||
```
|
||||
|
||||
### 4.2 팀 공유 가능 테이블 (meetings, meeting_memos, meeting_documents, transcripts)
|
||||
|
||||
```sql
|
||||
-- SELECT: 본인 소유 OR 소속 팀의 리소스
|
||||
CREATE POLICY "meetings_read" ON public.meetings
|
||||
FOR SELECT
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
OR team_id IN (
|
||||
SELECT team_id FROM public.team_members WHERE user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- INSERT: 본인 user_id 강제
|
||||
CREATE POLICY "meetings_insert" ON public.meetings
|
||||
FOR INSERT
|
||||
WITH CHECK (user_id = auth.uid());
|
||||
|
||||
-- UPDATE: 본인 소유 OR 팀 admin 이상
|
||||
CREATE POLICY "meetings_update" ON public.meetings
|
||||
FOR UPDATE
|
||||
USING (
|
||||
user_id = auth.uid()
|
||||
OR team_id IN (
|
||||
SELECT team_id FROM public.team_members
|
||||
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')
|
||||
)
|
||||
);
|
||||
|
||||
-- DELETE: 본인 소유만
|
||||
CREATE POLICY "meetings_delete" ON public.meetings
|
||||
FOR DELETE
|
||||
USING (user_id = auth.uid());
|
||||
```
|
||||
|
||||
### 4.3 팀 자체 (teams, team_members)
|
||||
|
||||
```sql
|
||||
-- 팀 조회: 본인이 소유 OR 멤버
|
||||
CREATE POLICY "teams_read" ON public.teams
|
||||
FOR SELECT
|
||||
USING (
|
||||
owner_id = auth.uid()
|
||||
OR id IN (SELECT team_id FROM public.team_members WHERE user_id = auth.uid())
|
||||
);
|
||||
|
||||
-- 팀 생성: 누구나 (owner = 본인)
|
||||
CREATE POLICY "teams_insert" ON public.teams
|
||||
FOR INSERT WITH CHECK (owner_id = auth.uid());
|
||||
|
||||
-- 팀 수정: owner만
|
||||
CREATE POLICY "teams_update" ON public.teams
|
||||
FOR UPDATE USING (owner_id = auth.uid());
|
||||
```
|
||||
|
||||
## 5. Auth 트리거
|
||||
|
||||
### 5.1 `handle_new_user` — auth.users → profiles 자동 생성
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (id, name, avatar_url, locale)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
COALESCE(NEW.raw_user_meta_data->>'full_name', NEW.raw_user_meta_data->>'name', split_part(NEW.email, '@', 1)),
|
||||
NEW.raw_user_meta_data->>'avatar_url',
|
||||
COALESCE(NEW.raw_user_meta_data->>'locale', 'ko')
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
```
|
||||
|
||||
### 5.2 `moddatetime` — updated_at 자동 갱신
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION public.moddatetime()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 각 테이블마다:
|
||||
CREATE TRIGGER set_updated_at
|
||||
BEFORE UPDATE ON public.meetings
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
```
|
||||
|
||||
## 6. Edge Functions
|
||||
|
||||
### 6.1 공통: `_shared/auth.ts`
|
||||
|
||||
```typescript
|
||||
import { createClient, User } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
export async function requireUser(req: Request): Promise<User> {
|
||||
const authHeader = req.headers.get('Authorization')
|
||||
if (!authHeader) throw new Response('Unauthorized', { status: 401 })
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
Deno.env.get('SUPABASE_ANON_KEY')!,
|
||||
{ global: { headers: { Authorization: authHeader } } }
|
||||
)
|
||||
|
||||
const { data: { user }, error } = await supabase.auth.getUser()
|
||||
if (error || !user) throw new Response('Unauthorized', { status: 401 })
|
||||
return user
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 `stt-proxy/` — Google Cloud STT 래퍼
|
||||
|
||||
- 입력: `multipart/form-data` (audio blob + sample_rate + language_code)
|
||||
- 인증: JWT → user 추출
|
||||
- 쿼터: `daily_usage` 확인 → 초과 시 429
|
||||
- 호출: Google Cloud Speech-to-Text API (v2 recognize)
|
||||
- 응답: `{ transcript, confidence, language_code, duration_seconds }`
|
||||
- 사이드 이펙트: `daily_usage.count` 증가
|
||||
|
||||
### 6.3 `llm-proxy/` — Claude API 래퍼
|
||||
|
||||
- 입력: `application/json` (messages + system + max_tokens + model)
|
||||
- 인증: 동일
|
||||
- 쿼터: 동일 (`llm_process` feature)
|
||||
- 호출: Anthropic Messages API (스트리밍 지원)
|
||||
- 응답: SSE 스트림 또는 JSON
|
||||
- 모델 정책: Free → Haiku 강제, Pro+ → Sonnet 허용
|
||||
|
||||
## 7. Storage 버킷
|
||||
|
||||
```sql
|
||||
INSERT INTO storage.buckets (id, name, public) VALUES
|
||||
('audio', 'audio', false), -- 녹음 원본 (비공개)
|
||||
('exports', 'exports', false), -- PDF/DOCX 내보내기
|
||||
('avatars', 'avatars', true); -- 프로필 사진 (공개)
|
||||
```
|
||||
|
||||
정책: 파일 경로가 `{user_id}/...`로 시작해야 쓰기 허용.
|
||||
|
||||
## 8. 실시간 구독 전략
|
||||
|
||||
- 회의 중 `transcripts` INSERT → Realtime 구독으로 팀원에게 전파
|
||||
- 회의록 편집 중 `meeting_documents` UPDATE → CRDT 병합 (V2-7 팀 기능에서 구현)
|
||||
|
||||
## 9. 검증 전략
|
||||
|
||||
V2-2는 로컬에서 전체 검증 불가 (Supabase 프로젝트 필요). 이 페이즈 완료 판정 기준:
|
||||
|
||||
1. ✅ SQL 마이그레이션 파일 작성 완료
|
||||
2. ✅ Edge Functions 스캐폴딩 완료 (TypeScript 컴파일 가능)
|
||||
3. ✅ 설정 가이드 문서 완료
|
||||
4. ⏳ 실제 deploy 검증은 사용자가 `supabase db push` 후 진행 (V2-2b)
|
||||
|
||||
## 10. 다음 페이즈와의 관계
|
||||
|
||||
- **V2-3 (Web App)**: Next.js에서 `@supabase/supabase-js` 클라이언트로 직접 연결
|
||||
- **V2-4 (데스크톱 동기화)**: apps/desktop에 supabase 클라이언트 추가, 로컬 SQLite ↔ Postgres 양방향 sync 레이어
|
||||
- **V2-7 (팀 기능)**: teams/team_members 테이블 + invite flow
|
||||
Loading…
Add table
Add a link
Reference in a new issue