-- ============================================================================ -- Phase 3.2: Tier 통일 (team → pro_plus) + 오버리지 크레딧 데이터 모델 -- ============================================================================ -- 데스크톱 코드가 이미 'pro_plus'를 사용 중이고 서버만 'team'이 남아있어 -- 발생한 불일치를 해소. 팀 협업 feature(teams/team_members 테이블)와 -- 가격제(tier) 개념은 분리 — 이 마이그레이션은 가격제만 건드린다. -- -- 추가로 SaaS 오버리지 구매 모델을 위한 subscriptions.overage_credits 컬럼 -- 도입. 실제 Stripe 연결은 Phase 3.3 이월, 이번 마이그레이션은 데이터 모델과 -- 읽기 경로만 준비. -- ---------------------------------------------------------------------------- -- 1. profiles.tier: 기존 CHECK 제약 제거 → 값 마이그레이션 → 새 CHECK -- ---------------------------------------------------------------------------- ALTER TABLE public.profiles DROP CONSTRAINT IF EXISTS profiles_tier_check; UPDATE public.profiles SET tier = 'pro_plus' WHERE tier = 'team'; ALTER TABLE public.profiles ADD CONSTRAINT profiles_tier_check CHECK (tier IN ('free', 'pro', 'pro_plus')); -- ---------------------------------------------------------------------------- -- 2. subscriptions.tier: 동일 처리 -- ---------------------------------------------------------------------------- ALTER TABLE public.subscriptions DROP CONSTRAINT IF EXISTS subscriptions_tier_check; UPDATE public.subscriptions SET tier = 'pro_plus' WHERE tier = 'team'; ALTER TABLE public.subscriptions ADD CONSTRAINT subscriptions_tier_check CHECK (tier IN ('free', 'pro', 'pro_plus')); -- ---------------------------------------------------------------------------- -- 3. subscriptions.overage_credits: SaaS 오버리지 구매 모델 -- ---------------------------------------------------------------------------- ALTER TABLE public.subscriptions ADD COLUMN IF NOT EXISTS overage_credits integer NOT NULL DEFAULT 0; COMMENT ON COLUMN public.subscriptions.overage_credits IS '추가 크레딧 (베이스 일일 쿼터 소진 시 차감). Phase 3.2는 데이터 모델만, 실제 Stripe 구매 경로는 Phase 3.3.'; -- ---------------------------------------------------------------------------- -- 4. consume_quota RPC: 원자적 base → overage fallback 소비 -- ---------------------------------------------------------------------------- -- 기존 increment_daily_usage는 유지 (다른 경로에서 쓰일 수 있음). -- llm-proxy는 이 새 RPC를 사용해 원자적으로 base → overage 순차 소비. CREATE OR REPLACE FUNCTION public.consume_quota( p_user_id uuid, p_feature text, p_base_limit integer ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_current integer; v_overage integer; v_new_count integer; v_new_overage integer; BEGIN -- 오늘 사용량 조회 (없으면 0) SELECT count INTO v_current FROM public.daily_usage WHERE user_id = p_user_id AND feature = p_feature AND date = CURRENT_DATE; v_current := COALESCE(v_current, 0); -- 오버리지 크레딧 조회 (없으면 0) SELECT overage_credits INTO v_overage FROM public.subscriptions WHERE user_id = p_user_id; v_overage := COALESCE(v_overage, 0); -- 무제한(-1): 그냥 카운터만 증가 IF p_base_limit = -1 THEN INSERT INTO public.daily_usage (user_id, date, feature, count) VALUES (p_user_id, CURRENT_DATE, p_feature, 1) ON CONFLICT (user_id, date, feature) DO UPDATE SET count = public.daily_usage.count + 1 RETURNING count INTO v_new_count; RETURN jsonb_build_object( 'allowed', true, 'current', v_new_count, 'limit', -1, 'overage_credits', v_overage, 'consumed_from', 'unlimited' ); END IF; -- base 잔여 여부 체크 IF v_current < p_base_limit THEN -- base 소비 INSERT INTO public.daily_usage (user_id, date, feature, count) VALUES (p_user_id, CURRENT_DATE, p_feature, 1) ON CONFLICT (user_id, date, feature) DO UPDATE SET count = public.daily_usage.count + 1 RETURNING count INTO v_new_count; RETURN jsonb_build_object( 'allowed', true, 'current', v_new_count, 'limit', p_base_limit, 'overage_credits', v_overage, 'consumed_from', 'base' ); END IF; -- base 소진 → 오버리지 체크 IF v_overage <= 0 THEN RETURN jsonb_build_object( 'allowed', false, 'current', v_current, 'limit', p_base_limit, 'overage_credits', 0, 'consumed_from', 'none' ); END IF; -- 오버리지 소비 (daily_usage 증가 + overage_credits 감소) UPDATE public.subscriptions SET overage_credits = overage_credits - 1, updated_at = now() WHERE user_id = p_user_id RETURNING overage_credits INTO v_new_overage; INSERT INTO public.daily_usage (user_id, date, feature, count) VALUES (p_user_id, CURRENT_DATE, p_feature, 1) ON CONFLICT (user_id, date, feature) DO UPDATE SET count = public.daily_usage.count + 1 RETURNING count INTO v_new_count; RETURN jsonb_build_object( 'allowed', true, 'current', v_new_count, 'limit', p_base_limit, 'overage_credits', v_new_overage, 'consumed_from', 'overage' ); END; $$; COMMENT ON FUNCTION public.consume_quota(uuid, text, integer) IS '원자적 쿼터 소비: base 먼저 → 소진 시 overage. Phase 3.2 llm-proxy 전용.'; -- RPC는 service_role만 호출 가능 REVOKE ALL ON FUNCTION public.consume_quota(uuid, text, integer) FROM public; GRANT EXECUTE ON FUNCTION public.consume_quota(uuid, text, integer) TO service_role;