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
120
server/supabase/migrations/20260409000003_auth_triggers.sql
Normal file
120
server/supabase/migrations/20260409000003_auth_triggers.sql
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
-- ============================================================================
|
||||
-- Phase V2-2: Auth 트리거 + updated_at 자동 갱신
|
||||
-- ============================================================================
|
||||
|
||||
-- ============================================================================
|
||||
-- handle_new_user: auth.users → public.profiles + public.subscriptions 생성
|
||||
-- ============================================================================
|
||||
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')
|
||||
);
|
||||
|
||||
INSERT INTO public.subscriptions (user_id, tier, status)
|
||||
VALUES (NEW.id, 'free', 'active');
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
|
||||
-- ============================================================================
|
||||
-- handle_user_deleted: auth.users 삭제 시 cleanup
|
||||
-- (FK CASCADE로 자동이지만 추가 정리 지점으로 남겨둠)
|
||||
-- ============================================================================
|
||||
-- 현재는 FK CASCADE에 의존, 필요 시 확장.
|
||||
|
||||
-- ============================================================================
|
||||
-- moddatetime: updated_at 자동 갱신 트리거 함수
|
||||
-- ============================================================================
|
||||
CREATE OR REPLACE FUNCTION public.moddatetime()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 각 테이블에 트리거 부착 ----------------------------------------------------
|
||||
CREATE TRIGGER set_updated_at_profiles
|
||||
BEFORE UPDATE ON public.profiles
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_teams
|
||||
BEFORE UPDATE ON public.teams
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_meetings
|
||||
BEFORE UPDATE ON public.meetings
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_meeting_documents
|
||||
BEFORE UPDATE ON public.meeting_documents
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_transcripts
|
||||
BEFORE UPDATE ON public.transcripts
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_history
|
||||
BEFORE UPDATE ON public.history
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_dictionary
|
||||
BEFORE UPDATE ON public.dictionary
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
CREATE TRIGGER set_updated_at_subscriptions
|
||||
BEFORE UPDATE ON public.subscriptions
|
||||
FOR EACH ROW EXECUTE FUNCTION public.moddatetime();
|
||||
|
||||
-- ============================================================================
|
||||
-- increment_daily_usage: Edge Function에서 호출하는 쿼터 증가 헬퍼
|
||||
-- service_role 전용 (SECURITY DEFINER)
|
||||
-- ============================================================================
|
||||
CREATE OR REPLACE FUNCTION public.increment_daily_usage(
|
||||
p_user_id uuid,
|
||||
p_feature text,
|
||||
p_amount integer DEFAULT 1
|
||||
)
|
||||
RETURNS integer
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_new_count integer;
|
||||
BEGIN
|
||||
INSERT INTO public.daily_usage (user_id, date, feature, count)
|
||||
VALUES (p_user_id, CURRENT_DATE, p_feature, p_amount)
|
||||
ON CONFLICT (user_id, date, feature)
|
||||
DO UPDATE SET count = public.daily_usage.count + p_amount
|
||||
RETURNING count INTO v_new_count;
|
||||
|
||||
RETURN v_new_count;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 일반 유저는 호출 불가, service_role만 허용
|
||||
REVOKE ALL ON FUNCTION public.increment_daily_usage FROM PUBLIC;
|
||||
REVOKE ALL ON FUNCTION public.increment_daily_usage FROM authenticated;
|
||||
GRANT EXECUTE ON FUNCTION public.increment_daily_usage TO service_role;
|
||||
Loading…
Add table
Add a link
Reference in a new issue