Commit graph

15 commits

Author SHA1 Message Date
Yun Chan
bb0e54dcee feat(server): deliver push without a Firebase project
Every notification depended on Firebase Cloud Messaging, so a missing
Firebase project, which is the current state, meant no notification could be
delivered on any platform. Web Push and token-based Apple Push are now first
class transports alongside FCM, chosen per registered device, and a scheduled
Cloudflare Worker drain retries an outbox so a provider outage delays rather
than drops a message.
2026-09-16 23:24:44 +09:00
Yun Chan
5205dcdfa9 feat(release): prepare 1.1.0 candidate 2026-08-29 18:33:45 +09:00
윤찬
8af75a0a1e feat: V2-7 Admin 콘솔 리디자인 + 3단계 권한 + SaaS 전환 + 코드 정리
- Admin CRM: D3RO Console 스타일 전체 적용 (panelSx/tableSx/filterBtnSx)
- 3단계 권한: manager/admin/super_admin (DB + Edge Functions + Frontend)
- 랜딩 페이지: 1회 결제 → 월간/연간 구독 SaaS 모델 (10개 언어)
- SSE 스트리밍: VoiceConversation Premium LLM 라우팅 + fallback
- Supabase 클라이언트: packages/api-client 공통 추출 (browser+server)
- RPC 함수 타입: 9개 정의 (admin_usage_by_feature 등)
- callAdminApi 401 버그 수정 (getUser() 선행 토큰 갱신)
2026-04-13 01:28:10 +09:00
윤찬
dca1b90faa feat(admin): Phase V2-6 Admin CRM 고도화 — CRUD + 차트 + 결제 + 감사
- DB: audit_log 테이블(diff 포함) + subscriptions.admin_note + super_admin role
- Edge Functions 4개: admin-users, admin-subscriptions, admin-payments, admin-audit-log
- 공유 유틸: admin-auth.ts(권한 검증), audit.ts(감사로그 기록)
- Swagger UI: 독립 정적 페이지 + OpenAPI 3.0 spec
- CRUD 페이지: 구독 생성/수정/삭제, role 변경, 감사로그 목록/상세
- recharts: feature별 StackedBar + DAU Line + Top Users HorizontalBar
- 결제 이력: DB + Payple API 병행 조회
- 권한: super_admin만 위험 작업, admin은 조회 전용
- RLS: admin/super_admin IN 정책 + super_admin 쓰기 정책
- SQL RPC: admin_usage_by_feature, admin_top_users, admin_dau
2026-04-12 21:32:47 +09:00
윤찬
eb4c504fea fix(admin): RLS 재귀 수정 + OAuth 쿠키 + 코드 정리
- RLS: profiles 재귀 참조 → app_metadata 기반으로 교체
- auth callback: 쿠키를 response에 직접 설정 (세션 유지)
- middleware: Supabase 세션 갱신 추가
- layout: requireAdmin() 사용으로 통합 (중복 제거)
- admin-guard: app_metadata.role 기반 (JWT, DB 쿼리 불필요)
- debug 라우트 제거
- config.toml: localhost:3000/3001 redirect URL 추가
2026-04-12 20:52:56 +09:00
윤찬
667c09242b feat(web): 관리자 CRM 웹페이지 — admin role + 4개 페이지
- migration: profiles.role 컬럼 + admin RLS 정책
- admin-guard.ts: RSC용 admin 권한 체크
- /admin: CRM 대시보드 (총 유저, 유료 구독자, API 호출, 만료 예정)
- /admin/users: 유저 목록 (검색/필터/페이지네이션)
- /admin/users/[id]: 유저 상세 (프로필+구독+30일 사용량)
- /admin/subscriptions: 구독 목록 (상태 필터)
- /admin/usage: 사용량 집계 (7/14/30일)
- Sidebar에 Admin 네비게이션 추가
2026-04-12 20:24:26 +09:00
윤찬
d3c2a4348d feat(server): Payple 정기 갱신 크론 — payple-renew Edge Function + GitHub Actions
- payple-renew: 만료 구독 조회 → paypleBilling → 기간 갱신
- 3회 연속 실패 시 tier=free, status=expired 다운그레이드
- migration: subscriptions.renewal_failures 컬럼 추가
- payple-webhook: 결제 완료 시 renewal_failures=0 리셋
- GitHub Actions: 매일 01:00 UTC 스케줄
2026-04-12 20:18:11 +09:00
윤찬
c9baf031c9 feat(server+web+desktop): Phase 3.2-B Payple 결제 연동 + 대시보드 Premium 상태 표시
Payple PG 연동:
- _shared/payple.ts: Payple API 래퍼 (auth/billing/cancel/deleteBillingKey)
- payple-checkout Edge Function: 빌링키 결제 + 구독 활성화
- payple-webhook Edge Function: 결제완료/취소 이벤트
- payple-manage Edge Function: 구독 취소 (빌링키 해지)
- DB migration: payment_provider + payple_payer_id + payple_pay_oid
- 웹 billing 페이지: Payple JS SDK 결제창 + 관리 버튼 (Stripe 대체)
- Electron LicenseModal: shell.openExternal → 웹 결제 페이지

대시보드 Premium 상태:
- CrtDisplay services에 PREMIUM LLM LED 추가
- 백엔드 인디케이터 카드 (Local/Premium) + 티어 카드
- 사용량 섹션: 전 티어 표시 + 모델별 Premium 쿼터
- 12개 locale × 14개 i18n 키
2026-04-12 19:18:40 +09:00
윤찬
6e52c18e5b feat(desktop+server): Phase 3.2 Premium LLM — Anthropic Claude 프리미엄 파이프라인 + 모델별 쿼터 + SaaS UI
빅뱅 8/8 마지막 성공 기준 달성. Supabase Edge Function(llm-proxy)을 통해
Anthropic Claude를 호출하는 PremiumLLMService 신규 구현. 사용자가 Settings에서
Local/Premium 백엔드를 선택하면 VoiceModeService가 자동 분기하고, Premium 실패 시
Local로 silent fallback + 상단 중앙 배너 알림.

실측: Claude Haiku refine 1.6~3.2초 (이전 qwen3 42.9초 → 13~27배 빠름).

주요 변경:
- PremiumLLMService 신규 (싱글톤+EventEmitter, processText/chatStream,
  Supabase functions.invoke 기반, _ensureAuth 가드)
- llm-prompts.ts: SYSTEM_PROMPTS를 Local/Premium 공유 모듈로 추출
  (resolveSystemPrompt 헬퍼)
- VoiceModeService: _getLLMProcessor → _runProcessorWithFallback 라우터 +
  premium-llm-fallback 이벤트
- CloudSyncService: getAccessToken(async), getAnonKey, invokeFunction(auth
  헤더 자동 처리, 에러 body 파싱)
- IPC: LLM.PREMIUM_* 채널 6개 + preload API + llm-handlers 이벤트 전달
  (safeSendToRenderer 헬퍼)
- AppConfig.llmBackend: 'local' | 'premium' (기본 'local')
- Settings UI: Backend 드롭다운 + Premium 선택 시 Ollama UI 숨김 + 라이선스
  모달 자동 오픈
- AppLayout: 상단 중앙 Snackbar fallback 배너 (8초, warning filled)
- LicenseModal: 라이선스 키 입력 제거 → SaaS 구독 관리 UI 전환
  (Free/Pro/Pro+ 업그레이드 버튼, Payple 준비 중 스텁)
- 등급 비교 표: featureLabel i18n 번역 수정

서버 (Supabase Edge Functions):
- quota.ts: 모델별 쿼터 구조 (llm_haiku/sonnet/opus × free/pro/pro_plus),
  주간/일간 기간 분리, modelToQuotaKey 매핑, consumeQuota baseLimit 파라미터화
- llm-proxy: 모델별 쿼터 체크 + 소비 (checkQuota → consumeQuota 원자적),
  verify_jwt=false (2026 sb_publishable_ 키 호환)
- config.toml: llm-proxy verify_jwt = false
- migration 20260412000001: tier team→pro_plus 통일, subscriptions.overage_credits
  컬럼, consume_quota RPC (원자적 base→overage fallback)

Tier/쿼터:
- free: Haiku 250/주간, Sonnet/Opus 불가
- pro ₩9,900: Haiku 1500/일, Sonnet 300/일, Opus 50/일
- pro_plus ₩29,900: Haiku 무제한, Sonnet 1500/일, Opus 300/일
- api-client SubscriptionTier: team→pro_plus, overage_credits 필드 추가
2026-04-12 18:28:02 +09:00
윤찬
e55687d298 fix(desktop+supabase): 로컬 ID UUID 통일 + Realtime publication (빅뱅 Phase 5 Part 2)
SaaS [9] — 실증 A/B/C/D 전부 통과, 로컬→클라우드 push 최초 성공(pushed=1).

## Bug 4: 로컬 nanoid PK vs Supabase UUID PK 불일치
- 증상: Push history failed: invalid input syntax for type uuid: "fvy6bIzr..."
- 원인: 로컬 drizzle schema는 text PK + nanoid() 생성, Supabase는 uuid PK.
  빅뱅 사이클 내내 push가 한 번도 성공한 적 없었음 (지난 pushed=0은 데이터 0건이라서).
- 픽스: 로컬을 UUID로 통일 (근본 해결, 땜질 금지).
  14개 서비스 20곳 nanoid() → crypto.randomUUID() 일괄 교체.
  nanoid 의존성 + electron.vite.config exclude 제거.
  drizzle schema는 text PK 그대로 유지 (SQLite는 UUID 문자열 저장 가능).

## Bug 5: supabase_realtime publication 누락
- 증상: 로그인 직후 Realtime 채널 상태: TIMED_OUT
- 원인: initial_schema.sql이 transcripts 테이블만 publication에 추가.
  데스크톱이 구독하는 meetings/history/dictionary는 누락 → postgres_changes 흐르지 않음.
- 픽스: 20260411000002_realtime_publication.sql 신규.
  pg_publication_tables 카탈로그 체크 + 조건부 ADD TABLE (meetings/meeting_memos/
  meeting_documents/history/dictionary 5개). supabase db push 적용.

## Bug 6: persistSession:false에서 realtime.setAuth 자동 전파 안 됨 (부분 픽스)
- 픽스: CloudSyncService.startRealtime()에 client.realtime.setAuth(access_token)
  명시 호출 (채널 구성 이전).
- ⚠️ Bug 5+6 적용 후에도 Realtime 여전히 TIMED_OUT. 후속 조사 필요.
  블로커 아님 — 주기 pull + Phase 3.3 auto push로 최종 일관성 유지.

## 실증 결과
- A 세션 자동 복원: Restored session for yunchan8804@gmail.com → DB 재오픈
- B push 경로: HistoryService created 56a767ac-... → Sync complete pushed=1 errors=0
- C 로그아웃 복귀: Realtime 종료 → users/_local/d3ro.db 복귀 → local mode
- D 재로그인 복원: 실증 A의 restore 경로와 동일, 같은 uuid DB 파일 보존
- E 웹 크로스 디바이스: Phase 3.3 이후로 지연 (Realtime 이슈 별건)

검증: desktop tsc --noEmit , dev 재기동 , push 최초 성공 
2026-04-11 18:46:22 +09:00
윤찬
9498380f7f fix(desktop+supabase): OAuth 로그인 완주 + RLS 재귀 픽스 (빅뱅 Phase 5 Part 1)
빅뱅 Phase 5 실증 중 발견한 3개 버그 해결:

1. implicit flow 응답 무시:
   - supabase-js가 persistSession:false 일 때 PKCE code_verifier 저장 못해
     implicit flow로 fallback, fragment(#access_token=...&refresh_token=...)로
     토큰 전달. handleDeepLink는 ?code=만 파싱하고 있어 무시됨.
   - handleDeepLink에 fragment parser 추가 (access_token + refresh_token)
   - CloudSyncService.handleAuthTokens() 신규 — setSession() 후
     기존 _onAuthenticated SSOT로 수렴
   - 에러 query/fragment(error, error_description) 상세 로깅
   - 토큰 값은 로그에 안 찍히도록 (query=yes/no, fragment=yes/no 만)

2. Supabase RLS 무한 재귀:
   - team_members_read_same_team 정책이 자기 테이블 재조회 →
     Postgres RLS 엔진 무한 재귀 탐지 에러
   - meetings / meeting_memos / meeting_documents / team_invites 정책이
     team_members 서브쿼리 경유해서 전부 같이 터짐 + Realtime TIMED_OUT
   - migration 20260411000001: SECURITY DEFINER 함수 2개
     (user_team_ids, user_admin_team_ids) 신규 — Supabase 권장 패턴
   - 영향 정책: team_members(4) + meetings(2) + meeting_memos(1) +
     meeting_documents(2) + team_invites(1) 전부 함수 기반으로 재작성
   - supabase db push 완료

3. LoginScreen stale 번들:
   - Phase 1.5에서 import 제거했는데도 vite HMR/cache 어딘가에서
     stale state 유지해서 렌더러에 계속 뜸
   - LoginScreen.tsx 파일 자체 삭제 (vite 컴파일 대상 제거)

로그인 성공 확인: yunchan8804@gmail.com 으로 Google OAuth 완주 →
users/7da3dd02-9f2f-4ee9-a9b3-1c2c24875a93/d3ro.db 생성 → Initial sync 시작.
2026-04-11 11:55:42 +09:00
yunchan8804
b386733d1e feat(V2-5차): pgvector 시맨틱 검색 + chat 스트리밍 + doc 편집 + VoiceAction + Realtime + storage + 메모
묶음 Q — pgvector + 시맨틱 검색:
- migrations/20260410000004_pgvector_knowledge.sql
  - vector extension, embedding vector(1536) 컬럼
  - ivfflat cosine 인덱스
  - match_knowledge_chunks(query_embedding, match_count, similarity_threshold) RPC
  - RLS: user_id 또는 소속 팀 기준
- functions/embed-chunks: 문서 소유권 확인 후 OpenAI text-embedding-3-small 배치 호출 → knowledge_chunks.embedding 업데이트
- functions/search-knowledge: 쿼리 텍스트 → OpenAI 임베딩 → user 권한 RPC 호출 → 상위 청크 반환
- config.toml에 embed-chunks/search-knowledge 등록
- components/knowledge/knowledge-search.tsx: 검색창 + 결과 카드(유사도 %)
- /knowledge 페이지에 검색 UI 추가

묶음 R — /chat SSE 스트리밍:
- functions/llm-proxy: Anthropic Messages API stream 지원
  - ANTHROPIC_API_KEY 없으면 SSE placeholder 스트림
  - stream=true일 때 response.body 그대로 프록시 (text/event-stream)
  - stream=false는 JSON 응답
- components/chat/chat-panel.tsx:
  - stream=true로 요청
  - ReadableStream 파싱 (SSE: data: {type:content_block_delta, delta:{text_delta}})
  - assistantId 메시지를 progressive 업데이트, scrollToBottom
  - 불필요한 LlmResponse 인터페이스 제거

묶음 S — DocumentEditor:
- components/meetings/document-editor.tsx
  - 문서 박스 클릭 → MUI Dialog (fullWidth, maxWidth md)
  - TextField multiline 20~40 rows, monospace
  - 제목 편집 + 저장/삭제 버튼
- meetings/[id] 페이지 Documents 섹션을 DocumentEditor로 교체 (+ typoSx 미사용 import 제거)

묶음 T — /actions (VoiceAction 이식):
- components/actions/action-runner.tsx
  - SYSTEM_PROMPT로 JSON 스키마 강제 (create_meeting/search_knowledge/create_memo/send_team_invite/unknown)
  - LLM 응답에서 JSON 추출 → 파싱 → 확인 후 실행
  - 각 type별 실행 로직 (meetings/memos INSERT, 검색은 안내만)
- app/(app)/actions/page.tsx + Sidebar Actions 메뉴 + AutoAwesomeIcon

묶음 U — Realtime + Storage + 메모 UI:
- CloudSyncService:
  - RealtimeChannel import 추가
  - startRealtime(): meetings/history/dictionary 변경 구독, debounce 후 pullAll 자동 트리거
  - stopRealtime(), signIn 직후/세션 복원 시 자동 시작, signOut 시 종료
- apps/web/components/meetings/memo-form.tsx
  - TextField + 저장 버튼, 회의 시작 기준 경과 ms 자동 계산
  - Realtime 구독자에게 자동 전파
- meetings/[id] MEMOS 섹션에 MemoForm 렌더
- apps/web/components/record/mic-recorder.tsx
  - STT 성공 후 Supabase Storage 'audio' 버킷에 {user_id}/{ts}.webm 업로드
  - meetings 테이블에 INSERT (raw_transcript, audio_storage_key, duration_ms, ended_at)
  - Storage/meetings 실패는 전사 결과는 유지하며 경고

묶음 V — 11개 locale nav 키:
- en/ja/zh/zh-TW/es/fr/de/pt/ru/vi/th 에 nav.chat/nav.knowledge/nav.actions 추가
- ko.json에 nav.actions 추가
- Sidebar에 Actions 메뉴(AutoAwesomeIcon) 등록

검증:
- desktop typecheck + build OK
- web typecheck + build OK (15 라우트: 기존 14 + /actions)
- api-client test 19 passed
- 회귀 없음

통계:
- 총 Edge Functions 12개 (embed-chunks/search-knowledge 추가)
- 총 SQL 마이그레이션 8개
- 웹 라우트 15개 (accept-invite/actions/billing/chat/dashboard/knowledge/login/meetings/meetings[id]/record/teams/teams[id]/auth-callback/root/_not-found)
2026-04-10 09:30:41 +09:00
yunchan8804
1fa24ce3c9 feat(V2-4차): Database 제네릭 복원 + /chat + /knowledge + Resend + Push + 문서 생성
묶음 K — Database 제네릭 완전 주입:
- @supabase/ssr 0.5→0.10, @supabase/supabase-js 2.45→2.103 bump
- 버전 정합으로 @supabase/ssr의 Database 제약이 정상 동작
- packages/api-client/src/types.ts:
  - TypedTable<Row, Insert, Update>로 Row & Record<string, unknown> 교차 유지
  - 각 테이블 Insert를 Partial<Row> & Pick<필수필드>로 교체
    (optional 컬럼이 required로 추론되던 문제 해결)
  - push_tokens / team_invites 테이블 타입도 inline 추가
- index.ts: types + client + auth + meetings + history + usage 전체 re-export 복원
- apps/web supabase-browser/server에 <Database> 제네릭 주입

묶음 L — /chat 페이지 (Voice Conversation 이식):
- apps/web/src/components/chat/chat-panel.tsx (client)
  - 메시지 state, user/assistant 말풍선, MetalCard 래핑
  - llm-proxy POST (non-streaming JSON), Anthropic 응답 형식 파싱
  - Enter 전송, Shift+Enter 줄바꿈, 초기화 버튼
- apps/web/src/app/(app)/chat/page.tsx
- Sidebar에 Chat 메뉴 추가 (ChatIcon)
- ko.json nav.chat 키

묶음 M — /knowledge 페이지 (RAG 이식):
- migrations/20260410000002_knowledge_documents.sql
  - knowledge_documents + knowledge_chunks 테이블
  - tsvector 자동 생성 컬럼 + GIN 인덱스 (전문 검색)
  - RLS: 개인/팀 분기 (team_id NULL 가능)
  - pgvector는 주석 처리 (V2-M+1에서 활성화)
- apps/web/src/app/(app)/knowledge/page.tsx — 카드 리스트 + 상태 배지
- components/knowledge/add-knowledge-form.tsx — 제목/타입/본문 입력,
  800자 단위 청킹 후 documents+chunks INSERT
- api-client types.ts에 KnowledgeDocument/Chunk 추가 + Database 등록
- Sidebar Knowledge 메뉴 (LibraryBooksIcon) + ko.json 키

묶음 N — team-invite Resend 이메일:
- functions/team-invite/index.ts에 Resend API 호출 로직 추가
- RESEND_API_KEY 설정 시 HTML 이메일 발송 (팀 이름/초대자/버튼/만료)
- 응답에 email_sent / email_error 포함
- API 키 없으면 기존대로 URL만 반환

묶음 O — Expo Push notification:
- migrations/20260410000003_push_tokens.sql (user_id, token unique, platform)
- functions/send-push/index.ts: 호출자 인증 + 본인/팀 멤버 권한 체크,
  대상 사용자 push_tokens 조회, Expo Push API 배치 호출
- config.toml에 send-push 함수 등록
- apps/mobile/package.json에 expo-device + expo-notifications 추가
- apps/mobile/lib/push.ts: registerPushToken (권한 요청, projectId,
  Android 채널, Supabase upsert)
- auth-context.tsx에서 로그인 직후 자동 등록
- app.json plugins에 expo-notifications

묶음 P — meetings/[id] 문서 생성:
- apps/web/components/meetings/generate-document-button.tsx (client)
  - 4개 템플릿 메뉴 (minutes/report/idea-note/mindmap)
  - 각 템플릿별 systemPrompt 지정
  - llm-proxy 호출 → meeting_documents INSERT
  - latency 측정, prompt_used 기록
- meetings/[id]/page.tsx DOCUMENTS 섹션에 버튼 노출
  (transcript는 edited_transcript ?? raw_transcript 우선)

검증:
- desktop typecheck + build OK
- web typecheck + build OK (14 라우트: 기존 12 + chat + knowledge)
- api-client test 19 passed

통계:
- 총 Edge Functions 10개: stt/llm-proxy, stripe-checkout/portal/webhook,
  team-invite/accept, send-push
- 총 SQL 마이그레이션 7개
- 웹 라우트 14개, 모바일 화면 5개
- 테스트 19개 passed
2026-04-10 08:51:53 +09:00
yunchan8804
c167737198 feat(V2-3차): pull 확장 + invite flow + Realtime + 테스트 + mobile UI 교체
묶음 E — V2-4b pull 확장:
- CloudSyncService.pullAll()에 meetings/meeting_memos/meeting_documents 추가
- meetings: LWW, 모든 컬럼 매핑 (minutes_json JSON 직렬화)
- meeting_memos: immutable INSERT-only 전략
- meeting_documents: LWW UPDATE

묶음 F — api-client types:
- interface -> type alias 전환 (11개)
- Database 타입에 TypedTable<Row, Insert, Update> 유틸 도입
  (Row & Record<string, unknown> 교차로 GenericTable 제약 만족)
- @supabase/ssr 2.102는 supabase-js 버전 불일치로 제네릭 주입 불가 -
  다음 사이클로 이월, api-client index.ts는 types만 재수출

묶음 G — apps/mobile UI 교체:
- login.tsx: MetalCard + PhosphorText(hero/label) + PhysicalButton
- meetings.tsx: MetalCard + Led(status) + PhosphorText(body/meta)
- record.tsx: Led + PhosphorText + PhysicalButton
- profile.tsx: MetalCard + PhysicalButton(danger) + d3roNativePalette

묶음 H — V2-7b 이메일 invite flow:
- migrations/20260410000001_team_invites.sql
  - team_invites 테이블 (token, email, role, expires_at 7일)
  - RLS: 같은 팀 멤버 + 초대 이메일 소유자 SELECT,
    owner/admin만 INSERT/DELETE
  - generate_invite_token() SECURITY DEFINER RPC (service_role)
- functions/team-invite: 권한 체크 -> 토큰 생성 -> 초대 URL 반환
- functions/team-accept: 토큰 검증 -> expires_at/accepted_at/이메일 일치 ->
  team_members upsert -> 초대 accepted 표시
- config.toml에 team-invite/team-accept 함수 등록
- InviteMemberForm 재작성: 이메일 입력 + 역할 선택 -> URL 복사 UI
- /accept-invite 페이지 신규 (Suspense 내 useSearchParams + token 수락)

묶음 I — Realtime transcripts:
- apps/web/components/meetings/live-transcript-list.tsx (client)
  - supabase.channel('transcripts:meeting:${id}').on('postgres_changes')
  - INSERT -> 세그먼트 추가, UPDATE -> row 교체
  - 중복 방지 segment_index 기준
  - edited 배지 표시
- meetings/[id]/page.tsx의 transcript 섹션을 LiveTranscriptList로 교체

묶음 J — 테스트:
- packages/api-client/__tests__/client.test.ts (9 tests)
  - createD3roSupabaseClient, isClientConfigured 팩토리 검증
- packages/api-client/__tests__/types.test.ts (10 tests)
  - 모든 Row 타입 + 리터럴 union + Database keyof
- vitest.config.ts 신규
- apps/web/playwright.config.ts 신규 (baseURL, webServer dev 서버)
- apps/web/e2e/smoke.spec.ts 신규 (6 스모크 테스트)
- apps/web tsconfig exclude에 e2e/playwright.config.ts 추가
- package.json scripts: test, test:e2e, test:e2e:ui

검증:
- desktop typecheck OK
- web typecheck OK
- web next build OK (12 라우트, /accept-invite Suspense 적용)
- desktop build OK
- api-client test 19 passed
2026-04-10 08:34:52 +09:00
yunchan8804
97eb886ec3 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 따라 수행.
2026-04-08 15:21:08 +09:00