mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 08:36:44 +00:00
Changes
Co-authored-by: domfelipe <53182096+domfelipe@users.noreply.github.com>
This commit is contained in:
parent
df82201a2d
commit
ff1ec6b774
2 changed files with 757 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue