-- ============================================================================ -- Phase V2-7b: 팀 초대 플로우 — team_invites 테이블 -- 이메일 기반 초대 토큰. accept endpoint에서 토큰 검증 후 team_members에 추가. -- ============================================================================ CREATE TABLE public.team_invites ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), team_id uuid NOT NULL REFERENCES public.teams(id) ON DELETE CASCADE, invited_by uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, email text NOT NULL, role text NOT NULL CHECK (role IN ('admin', 'member')) DEFAULT 'member', token text NOT NULL UNIQUE, accepted_at timestamptz, accepted_by uuid REFERENCES auth.users(id) ON DELETE SET NULL, expires_at timestamptz NOT NULL DEFAULT (now() + interval '7 days'), created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX idx_team_invites_team_id ON public.team_invites(team_id); CREATE INDEX idx_team_invites_token ON public.team_invites(token); CREATE INDEX idx_team_invites_email ON public.team_invites(email); -- RLS ALTER TABLE public.team_invites ENABLE ROW LEVEL SECURITY; -- SELECT: 같은 팀 멤버는 초대 목록 조회 가능, 본인 이메일로 온 초대도 조회 가능 CREATE POLICY "team_invites_read" ON public.team_invites FOR SELECT USING ( team_id IN (SELECT team_id FROM public.team_members WHERE user_id = auth.uid()) OR lower(email) = lower((SELECT email FROM auth.users WHERE id = auth.uid())) ); -- INSERT: 팀 owner/admin만 초대 생성 CREATE POLICY "team_invites_insert_admin" ON public.team_invites FOR INSERT WITH CHECK ( invited_by = auth.uid() AND team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') ) ); -- DELETE: owner/admin 또는 초대 당사자 취소 CREATE POLICY "team_invites_delete" ON public.team_invites FOR DELETE USING ( invited_by = auth.uid() OR team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') ) ); -- UPDATE는 Edge Function(service_role)에서 accepted_at/accepted_by 세팅용 -- ============================================================================ -- 토큰 생성 헬퍼 (service_role RPC) — 암호학적 난수 -- ============================================================================ CREATE OR REPLACE FUNCTION public.generate_invite_token() RETURNS text LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_token text; BEGIN v_token := encode(gen_random_bytes(24), 'base64'); -- URL-safe 문자로 변환 v_token := replace(replace(replace(v_token, '+', '-'), '/', '_'), '=', ''); RETURN v_token; END; $$; REVOKE ALL ON FUNCTION public.generate_invite_token FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.generate_invite_token TO service_role;