From ff1ec6b774e851d37eb64813904608294c884372 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 01:47:26 +0000 Subject: [PATCH] Changes Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com> --- src/integrations/supabase/types.ts | 327 +++++++++++++ ...3_a959d812-8602-4f07-9ad8-c4241d682e66.sql | 430 ++++++++++++++++++ 2 files changed, 757 insertions(+) create mode 100644 supabase/migrations/20260418014723_a959d812-8602-4f07-9ad8-c4241d682e66.sql diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 14c8b74..89b7193 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -77,6 +77,20 @@ export type Database = { referencedRelation: "profiles" referencedColumns: ["id"] }, + { + foreignKeyName: "agent_instances_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_integration_limits" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "agent_instances_user_id_fkey" + columns: ["user_id"] + isOneToOne: true + referencedRelation: "user_jobs_limits" + referencedColumns: ["user_id"] + }, { foreignKeyName: "agent_instances_user_id_fkey" columns: ["user_id"] @@ -86,6 +100,63 @@ export type Database = { }, ] } + available_mcps: { + Row: { + available_in_plans: Json + created_at: string + description: string + display_order: number + icon_url: string + id: string + is_active: boolean + name: string + oauth_authorize_url: string + oauth_revoke_url: string | null + oauth_token_url: string + provider: string + required_scopes: Json + slug: string + supports_refresh_token: boolean + updated_at: string + } + Insert: { + available_in_plans?: Json + created_at?: string + description: string + display_order?: number + icon_url: string + id?: string + is_active?: boolean + name: string + oauth_authorize_url: string + oauth_revoke_url?: string | null + oauth_token_url: string + provider: string + required_scopes?: Json + slug: string + supports_refresh_token?: boolean + updated_at?: string + } + Update: { + available_in_plans?: Json + created_at?: string + description?: string + display_order?: number + icon_url?: string + id?: string + is_active?: boolean + name?: string + oauth_authorize_url?: string + oauth_revoke_url?: string | null + oauth_token_url?: string + provider?: string + required_scopes?: Json + slug?: string + supports_refresh_token?: boolean + updated_at?: string + } + Relationships: [] + } enterprise_leads: { Row: { company_name: string @@ -122,6 +193,44 @@ export type Database = { } Relationships: [] } + oauth_state_tokens: { + Row: { + consumed: boolean + created_at: string + expires_at: string + id: string + mcp_id: string + state_token: string + user_id: string + } + Insert: { + consumed?: boolean + created_at?: string + expires_at?: string + id?: string + mcp_id: string + state_token: string + user_id: string + } + Update: { + consumed?: boolean + created_at?: string + expires_at?: string + id?: string + mcp_id?: string + state_token?: string + user_id?: string + } + Relationships: [ + { + foreignKeyName: "oauth_state_tokens_mcp_id_fkey" + columns: ["mcp_id"] + isOneToOne: false + referencedRelation: "available_mcps" + referencedColumns: ["id"] + }, + ] + } paddle_webhook_events: { Row: { environment: string @@ -209,6 +318,7 @@ export type Database = { paddle_customer_id: string | null phone: string | null stripe_customer_id: string | null + timezone: string updated_at: string } Insert: { @@ -222,6 +332,7 @@ export type Database = { paddle_customer_id?: string | null phone?: string | null stripe_customer_id?: string | null + timezone?: string updated_at?: string } Update: { @@ -235,10 +346,79 @@ export type Database = { paddle_customer_id?: string | null phone?: string | null stripe_customer_id?: string | null + timezone?: string updated_at?: string } Relationships: [] } + scheduled_jobs: { + Row: { + action_prompt: string + agent_instance_id: string + auto_paused_reason: string | null + created_at: string + cron_expression: string + description: string | null + human_readable: string + id: string + last_run_at: string | null + name: string + natural_language_input: string + next_run_at: string | null + required_mcp_slugs: Json + status: string + timezone: string + updated_at: string + user_id: string + } + Insert: { + action_prompt: string + agent_instance_id: string + auto_paused_reason?: string | null + created_at?: string + cron_expression: string + description?: string | null + human_readable: string + id?: string + last_run_at?: string | null + name: string + natural_language_input: string + next_run_at?: string | null + required_mcp_slugs?: Json + status?: string + timezone?: string + updated_at?: string + user_id: string + } + Update: { + action_prompt?: string + agent_instance_id?: string + auto_paused_reason?: string | null + created_at?: string + cron_expression?: string + description?: string | null + human_readable?: string + id?: string + last_run_at?: string | null + name?: string + natural_language_input?: string + next_run_at?: string | null + required_mcp_slugs?: Json + status?: string + timezone?: string + updated_at?: string + user_id?: string + } + Relationships: [ + { + foreignKeyName: "scheduled_jobs_agent_instance_id_fkey" + columns: ["agent_instance_id"] + isOneToOne: false + referencedRelation: "agent_instances" + referencedColumns: ["id"] + }, + ] + } skill_test_runs: { Row: { created_at: string @@ -291,6 +471,20 @@ export type Database = { referencedRelation: "profiles" referencedColumns: ["id"] }, + { + foreignKeyName: "skill_test_runs_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_integration_limits" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "skill_test_runs_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_jobs_limits" + referencedColumns: ["user_id"] + }, { foreignKeyName: "skill_test_runs_user_id_fkey" columns: ["user_id"] @@ -339,6 +533,20 @@ export type Database = { referencedRelation: "profiles" referencedColumns: ["id"] }, + { + foreignKeyName: "skill_versions_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "user_integration_limits" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "skill_versions_created_by_fkey" + columns: ["created_by"] + isOneToOne: false + referencedRelation: "user_jobs_limits" + referencedColumns: ["user_id"] + }, { foreignKeyName: "skill_versions_created_by_fkey" columns: ["created_by"] @@ -407,6 +615,20 @@ export type Database = { referencedRelation: "profiles" referencedColumns: ["id"] }, + { + foreignKeyName: "skills_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_integration_limits" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "skills_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_jobs_limits" + referencedColumns: ["user_id"] + }, { foreignKeyName: "skills_user_id_fkey" columns: ["user_id"] @@ -510,6 +732,20 @@ export type Database = { referencedRelation: "profiles" referencedColumns: ["id"] }, + { + foreignKeyName: "subscriptions_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_integration_limits" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "subscriptions_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_jobs_limits" + referencedColumns: ["user_id"] + }, { foreignKeyName: "subscriptions_user_id_fkey" columns: ["user_id"] @@ -577,6 +813,20 @@ export type Database = { referencedRelation: "profiles" referencedColumns: ["id"] }, + { + foreignKeyName: "telegram_messages_log_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_integration_limits" + referencedColumns: ["user_id"] + }, + { + foreignKeyName: "telegram_messages_log_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "user_jobs_limits" + referencedColumns: ["user_id"] + }, { foreignKeyName: "telegram_messages_log_user_id_fkey" columns: ["user_id"] @@ -615,8 +865,85 @@ export type Database = { }, ] } + user_integrations: { + Row: { + access_token_vault_id: string | null + connected_account_email: string | null + connected_account_name: string | null + created_at: string + error_message: string | null + granted_scopes: Json + id: string + last_refreshed_at: string | null + mcp_id: string + refresh_token_vault_id: string | null + status: string + token_expires_at: string | null + updated_at: string + user_id: string + } + Insert: { + access_token_vault_id?: string | null + connected_account_email?: string | null + connected_account_name?: string | null + created_at?: string + error_message?: string | null + granted_scopes?: Json + id?: string + last_refreshed_at?: string | null + mcp_id: string + refresh_token_vault_id?: string | null + status?: string + token_expires_at?: string | null + updated_at?: string + user_id: string + } + Update: { + access_token_vault_id?: string | null + connected_account_email?: string | null + connected_account_name?: string | null + created_at?: string + error_message?: string | null + granted_scopes?: Json + id?: string + last_refreshed_at?: string | null + mcp_id?: string + refresh_token_vault_id?: string | null + status?: string + token_expires_at?: string | null + updated_at?: string + user_id?: string + } + Relationships: [ + { + foreignKeyName: "user_integrations_mcp_id_fkey" + columns: ["mcp_id"] + isOneToOne: false + referencedRelation: "available_mcps" + referencedColumns: ["id"] + }, + ] + } } Views: { + user_integration_limits: { + Row: { + current_integrations_count: number | null + max_integrations: number | null + plan_slug: string | null + user_id: string | null + } + Relationships: [] + } + user_jobs_limits: { + Row: { + current_jobs_count: number | null + max_jobs: number | null + plan_slug: string | null + user_id: string | null + } + Relationships: [] + } user_skill_limits: { Row: { current_skills_count: number | null diff --git a/supabase/migrations/20260418014723_a959d812-8602-4f07-9ad8-c4241d682e66.sql b/supabase/migrations/20260418014723_a959d812-8602-4f07-9ad8-c4241d682e66.sql new file mode 100644 index 0000000..4c97d0c --- /dev/null +++ b/supabase/migrations/20260418014723_a959d812-8602-4f07-9ad8-c4241d682e66.sql @@ -0,0 +1,430 @@ +-- ============================================================================ +-- FASE 4 — Integrações OAuth e Automações +-- ============================================================================ + +-- 1. Adiciona timezone em profiles +ALTER TABLE public.profiles + ADD COLUMN IF NOT EXISTS timezone text NOT NULL DEFAULT 'America/Sao_Paulo'; + +-- ============================================================================ +-- 2. TABELA: available_mcps (catálogo público) +-- ============================================================================ +CREATE TABLE public.available_mcps ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + slug text NOT NULL UNIQUE, + name text NOT NULL, + provider text NOT NULL, + description text NOT NULL, + icon_url text NOT NULL, + oauth_authorize_url text NOT NULL, + oauth_token_url text NOT NULL, + oauth_revoke_url text, + required_scopes jsonb NOT NULL DEFAULT '[]'::jsonb, + available_in_plans jsonb NOT NULL DEFAULT '[]'::jsonb, + supports_refresh_token boolean NOT NULL DEFAULT false, + display_order int NOT NULL DEFAULT 0, + is_active boolean NOT NULL DEFAULT true, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); + +ALTER TABLE public.available_mcps ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "MCPs disponíveis são públicos para autenticados" + ON public.available_mcps FOR SELECT + TO authenticated + USING (is_active = true); + +CREATE TRIGGER update_available_mcps_updated_at + BEFORE UPDATE ON public.available_mcps + FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); + +-- ============================================================================ +-- 3. TABELA: user_integrations +-- ============================================================================ +CREATE TABLE public.user_integrations ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + mcp_id uuid NOT NULL REFERENCES public.available_mcps(id) ON DELETE RESTRICT, + status text NOT NULL DEFAULT 'active' + CHECK (status IN ('active', 'expired', 'revoked', 'error')), + -- Tokens estão no Vault. Sem FK porque o schema vault não permite. + -- Integridade garantida pela aplicação. + access_token_vault_id uuid, + refresh_token_vault_id uuid, + token_expires_at timestamptz, + connected_account_email text, + connected_account_name text, + granted_scopes jsonb NOT NULL DEFAULT '[]'::jsonb, + error_message text, + last_refreshed_at timestamptz, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (user_id, mcp_id) +); + +CREATE INDEX idx_user_integrations_user_status + ON public.user_integrations (user_id, status); + +ALTER TABLE public.user_integrations ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Usuários veem suas próprias integrações" + ON public.user_integrations FOR SELECT + USING (auth.uid() = user_id); + +-- INSERT/UPDATE/DELETE só via service_role (Edge Functions) + +CREATE TRIGGER update_user_integrations_updated_at + BEFORE UPDATE ON public.user_integrations + FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); + +-- ============================================================================ +-- 4. TABELA: oauth_state_tokens (CSRF) +-- ============================================================================ +CREATE TABLE public.oauth_state_tokens ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + state_token text NOT NULL UNIQUE, + user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + mcp_id uuid NOT NULL REFERENCES public.available_mcps(id) ON DELETE CASCADE, + consumed boolean NOT NULL DEFAULT false, + expires_at timestamptz NOT NULL DEFAULT (now() + interval '10 minutes'), + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE INDEX idx_oauth_state_lookup + ON public.oauth_state_tokens (state_token, consumed); + +ALTER TABLE public.oauth_state_tokens ENABLE ROW LEVEL SECURITY; +-- Sem policies: só service_role acessa + +-- ============================================================================ +-- 5. TABELA: scheduled_jobs (automações) +-- ============================================================================ +CREATE TABLE public.scheduled_jobs ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + agent_instance_id uuid NOT NULL REFERENCES public.agent_instances(id) ON DELETE CASCADE, + name text NOT NULL CHECK (length(name) BETWEEN 1 AND 60), + description text CHECK (description IS NULL OR length(description) <= 200), + cron_expression text NOT NULL, + human_readable text NOT NULL, + natural_language_input text NOT NULL, + action_prompt text NOT NULL CHECK (length(action_prompt) BETWEEN 20 AND 2000), + required_mcp_slugs jsonb NOT NULL DEFAULT '[]'::jsonb, + status text NOT NULL DEFAULT 'active' + CHECK (status IN ('active', 'paused', 'error', 'archived')), + auto_paused_reason text, + last_run_at timestamptz, + next_run_at timestamptz, + timezone text NOT NULL DEFAULT 'America/Sao_Paulo', + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + UNIQUE (user_id, name) +); + +CREATE INDEX idx_scheduled_jobs_user_status + ON public.scheduled_jobs (user_id, status); + +ALTER TABLE public.scheduled_jobs ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Usuários veem suas próprias automações" + ON public.scheduled_jobs FOR SELECT + USING (auth.uid() = user_id); + +CREATE POLICY "Usuários criam suas próprias automações" + ON public.scheduled_jobs FOR INSERT + WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Usuários atualizam suas próprias automações" + ON public.scheduled_jobs FOR UPDATE + USING (auth.uid() = user_id); + +CREATE POLICY "Usuários deletam suas próprias automações" + ON public.scheduled_jobs FOR DELETE + USING (auth.uid() = user_id); + +CREATE TRIGGER update_scheduled_jobs_updated_at + BEFORE UPDATE ON public.scheduled_jobs + FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); + +-- ============================================================================ +-- 6. VIEW: user_integration_limits +-- ============================================================================ +CREATE VIEW public.user_integration_limits +WITH (security_invoker = true) +AS +SELECT + p.id AS user_id, + pl.slug AS plan_slug, + CASE pl.slug + WHEN 'basic' THEN 2 + WHEN 'starter' THEN 4 + WHEN 'professional' THEN 8 + WHEN 'enterprise' THEN 999999 + ELSE NULL + END AS max_integrations, + COALESCE(( + SELECT COUNT(*)::int + FROM public.user_integrations ui + WHERE ui.user_id = p.id + AND ui.status IN ('active', 'expired', 'error') + ), 0) AS current_integrations_count +FROM public.profiles p +LEFT JOIN public.subscriptions s + ON s.user_id = p.id + AND s.status IN ('active', 'trialing') + AND (s.current_period_end IS NULL OR s.current_period_end > now()) +LEFT JOIN public.plans pl ON pl.id = s.plan_id; + +-- ============================================================================ +-- 7. VIEW: user_jobs_limits +-- ============================================================================ +CREATE VIEW public.user_jobs_limits +WITH (security_invoker = true) +AS +SELECT + p.id AS user_id, + pl.slug AS plan_slug, + CASE pl.slug + WHEN 'basic' THEN 0 + WHEN 'starter' THEN 5 + WHEN 'professional' THEN 25 + WHEN 'enterprise' THEN 999999 + ELSE NULL + END AS max_jobs, + COALESCE(( + SELECT COUNT(*)::int + FROM public.scheduled_jobs sj + WHERE sj.user_id = p.id + AND sj.status != 'archived' + ), 0) AS current_jobs_count +FROM public.profiles p +LEFT JOIN public.subscriptions s + ON s.user_id = p.id + AND s.status IN ('active', 'trialing') + AND (s.current_period_end IS NULL OR s.current_period_end > now()) +LEFT JOIN public.plans pl ON pl.id = s.plan_id; + +-- ============================================================================ +-- 8. TRIGGER: enforce_integration_limit +-- ============================================================================ +CREATE OR REPLACE FUNCTION public.enforce_integration_limit() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_max int; + v_count int; + v_plan_slug text; + v_mcp_slug text; + v_available_in jsonb; + v_agent_active boolean; +BEGIN + -- 1. Agente ativo? + SELECT EXISTS ( + SELECT 1 FROM public.agent_instances ai + WHERE ai.user_id = NEW.user_id + AND ai.status = 'active' + ) INTO v_agent_active; + + IF NOT v_agent_active THEN + RAISE EXCEPTION 'Agente não está ativo. Complete o onboarding primeiro.' + USING ERRCODE = 'P0005'; + END IF; + + -- 2. Limites de plano + SELECT max_integrations, current_integrations_count, plan_slug + INTO v_max, v_count, v_plan_slug + FROM public.user_integration_limits + WHERE user_id = NEW.user_id; + + IF v_max IS NULL THEN + RAISE EXCEPTION 'Sem assinatura ativa para conectar integrações' + USING ERRCODE = 'P0001'; + END IF; + + -- 3. MCP disponível no plano? + SELECT slug, available_in_plans + INTO v_mcp_slug, v_available_in + FROM public.available_mcps + WHERE id = NEW.mcp_id; + + IF NOT (v_available_in ? v_plan_slug) THEN + RAISE EXCEPTION 'A integração % não está disponível no plano %', v_mcp_slug, v_plan_slug + USING ERRCODE = 'P0003'; + END IF; + + -- 4. Limite atingido? (só conta novos, não updates) + IF TG_OP = 'INSERT' AND v_count >= v_max THEN + RAISE EXCEPTION 'Limite de % integrações atingido para o plano %', v_max, v_plan_slug + USING ERRCODE = 'P0002'; + END IF; + + RETURN NEW; +END; +$$; + +CREATE TRIGGER enforce_integration_limit_trigger + BEFORE INSERT ON public.user_integrations + FOR EACH ROW + EXECUTE FUNCTION public.enforce_integration_limit(); + +-- ============================================================================ +-- 9. TRIGGER: enforce_job_limit +-- ============================================================================ +CREATE OR REPLACE FUNCTION public.enforce_job_limit() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + v_max int; + v_count int; + v_plan_slug text; + v_agent_active boolean; +BEGIN + -- 1. Agente ativo? + SELECT EXISTS ( + SELECT 1 FROM public.agent_instances ai + WHERE ai.user_id = NEW.user_id + AND ai.status = 'active' + ) INTO v_agent_active; + + IF NOT v_agent_active THEN + RAISE EXCEPTION 'Agente não está ativo. Complete o onboarding primeiro.' + USING ERRCODE = 'P0005'; + END IF; + + -- 2. Limites de plano + SELECT max_jobs, current_jobs_count, plan_slug + INTO v_max, v_count, v_plan_slug + FROM public.user_jobs_limits + WHERE user_id = NEW.user_id; + + IF v_max IS NULL THEN + RAISE EXCEPTION 'Sem assinatura ativa para criar automações' + USING ERRCODE = 'P0001'; + END IF; + + -- 3. Plano não permite automações + IF v_max = 0 THEN + RAISE EXCEPTION 'O plano % não permite automações. Faça upgrade para Starter.', v_plan_slug + USING ERRCODE = 'P0004'; + END IF; + + -- 4. Limite atingido + IF v_count >= v_max THEN + RAISE EXCEPTION 'Limite de % automações atingido para o plano %', v_max, v_plan_slug + USING ERRCODE = 'P0002'; + END IF; + + RETURN NEW; +END; +$$; + +CREATE TRIGGER enforce_job_limit_trigger + BEFORE INSERT ON public.scheduled_jobs + FOR EACH ROW + EXECUTE FUNCTION public.enforce_job_limit(); + +-- ============================================================================ +-- 10. TRIGGER: cleanup_expired_oauth_states (FOR EACH STATEMENT, barato) +-- ============================================================================ +CREATE OR REPLACE FUNCTION public.cleanup_expired_oauth_states() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +BEGIN + DELETE FROM public.oauth_state_tokens + WHERE expires_at < (now() - interval '1 day'); + RETURN NULL; +END; +$$; + +CREATE TRIGGER cleanup_expired_oauth_states_trigger + AFTER INSERT ON public.oauth_state_tokens + FOR EACH STATEMENT + EXECUTE FUNCTION public.cleanup_expired_oauth_states(); + +-- ============================================================================ +-- 11. SEED: 5 MCPs disponíveis +-- ============================================================================ +INSERT INTO public.available_mcps + (slug, name, provider, description, icon_url, oauth_authorize_url, oauth_token_url, oauth_revoke_url, + required_scopes, available_in_plans, supports_refresh_token, display_order) +VALUES + ( + 'google_workspace', + 'Google Workspace', + 'google', + 'Conecte Gmail, Calendar e Drive para que o Mika possa ler emails, criar eventos e acessar arquivos.', + 'https://cdn.simpleicons.org/google', + 'https://accounts.google.com/o/oauth2/v2/auth', + 'https://oauth2.googleapis.com/token', + 'https://oauth2.googleapis.com/revoke', + '["https://www.googleapis.com/auth/gmail.modify", "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"]'::jsonb, + '["basic", "starter", "professional", "enterprise"]'::jsonb, + true, + 1 + ), + ( + 'notion', + 'Notion', + 'notion', + 'Acesse páginas e bancos de dados do Notion para que o Mika possa criar, editar e consultar suas anotações.', + 'https://cdn.simpleicons.org/notion', + 'https://api.notion.com/v1/oauth/authorize', + 'https://api.notion.com/v1/oauth/token', + NULL, + '["read_content", "update_content", "insert_content"]'::jsonb, + '["basic", "starter", "professional", "enterprise"]'::jsonb, + false, + 2 + ), + ( + 'todoist', + 'Todoist', + 'todoist', + 'Gerencie suas tarefas no Todoist — peça ao Mika para criar lembretes, listar tarefas do dia e marcar como concluído.', + 'https://cdn.simpleicons.org/todoist', + 'https://todoist.com/oauth/authorize', + 'https://todoist.com/oauth/access_token', + 'https://api.todoist.com/sync/v9/access_tokens/revoke', + '["data:read_write"]'::jsonb, + '["starter", "professional", "enterprise"]'::jsonb, + false, + 3 + ), + ( + 'calcom', + 'Cal.com', + 'calcom', + 'Conecte sua agenda do Cal.com para que o Mika consulte horários disponíveis e crie reuniões automaticamente.', + 'https://cdn.simpleicons.org/caldotcom', + 'https://app.cal.com/auth/oauth2/authorize', + 'https://api.cal.com/v2/oauth/token', + 'https://api.cal.com/v2/oauth/revoke', + '["READ_BOOKING", "WRITE_BOOKING"]'::jsonb, + '["starter", "professional", "enterprise"]'::jsonb, + true, + 4 + ), + ( + 'microsoft_365', + 'Microsoft 365', + 'microsoft', + 'Acesse Outlook, Calendar e OneDrive corporativos. Ideal para times que usam Microsoft 365 no trabalho.', + 'https://cdn.simpleicons.org/microsoftoffice', + 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + 'https://login.microsoftonline.com/common/oauth2/logout', + '["offline_access", "Mail.ReadWrite", "Calendars.ReadWrite", "Files.ReadWrite", "User.Read"]'::jsonb, + '["professional", "enterprise"]'::jsonb, + true, + 5 + ); \ No newline at end of file