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 따라 수행.
83 lines
3.1 KiB
SQL
83 lines
3.1 KiB
SQL
-- ============================================================================
|
|
-- Phase V2-2: Storage 버킷 + 접근 정책
|
|
-- ============================================================================
|
|
|
|
-- 버킷 생성 ------------------------------------------------------------------
|
|
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
|
|
VALUES
|
|
('audio', 'audio', false, 524288000, ARRAY['audio/wav', 'audio/webm', 'audio/mpeg', 'audio/mp4', 'audio/ogg']),
|
|
('exports', 'exports', false, 52428800, ARRAY['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/markdown', 'text/html']),
|
|
('avatars', 'avatars', true, 5242880, ARRAY['image/jpeg', 'image/png', 'image/webp'])
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- ============================================================================
|
|
-- audio 버킷: 본인 경로 ({user_id}/...) 에서만 읽기/쓰기
|
|
-- ============================================================================
|
|
CREATE POLICY "audio_read_own" ON storage.objects
|
|
FOR SELECT USING (
|
|
bucket_id = 'audio'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "audio_insert_own" ON storage.objects
|
|
FOR INSERT WITH CHECK (
|
|
bucket_id = 'audio'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "audio_update_own" ON storage.objects
|
|
FOR UPDATE USING (
|
|
bucket_id = 'audio'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "audio_delete_own" ON storage.objects
|
|
FOR DELETE USING (
|
|
bucket_id = 'audio'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- exports 버킷: 동일 패턴
|
|
-- ============================================================================
|
|
CREATE POLICY "exports_read_own" ON storage.objects
|
|
FOR SELECT USING (
|
|
bucket_id = 'exports'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "exports_insert_own" ON storage.objects
|
|
FOR INSERT WITH CHECK (
|
|
bucket_id = 'exports'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "exports_delete_own" ON storage.objects
|
|
FOR DELETE USING (
|
|
bucket_id = 'exports'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- avatars 버킷: 공개 읽기, 본인만 쓰기
|
|
-- ============================================================================
|
|
CREATE POLICY "avatars_read_public" ON storage.objects
|
|
FOR SELECT USING (bucket_id = 'avatars');
|
|
|
|
CREATE POLICY "avatars_insert_own" ON storage.objects
|
|
FOR INSERT WITH CHECK (
|
|
bucket_id = 'avatars'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "avatars_update_own" ON storage.objects
|
|
FOR UPDATE USING (
|
|
bucket_id = 'avatars'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|
|
|
|
CREATE POLICY "avatars_delete_own" ON storage.objects
|
|
FOR DELETE USING (
|
|
bucket_id = 'avatars'
|
|
AND auth.uid()::text = (storage.foldername(name))[1]
|
|
);
|