/**
 * src/db/migrations/007-add-owner-id-and-rls.ts
 * Migration: tenant ownership column + Row Level Security on every table.
 *
 * Why this exists:
 *   - tenantMiddleware in src/auth/middleware.ts already references tenants.owner_id,
 *     but no schema migration ever added the column. The check `if (tenant.owner_id
 *     && tenant.owner_id !== user.id)` is a silent no-op today — any authenticated
 *     user can hit /api/tenants/:id/* for any tenant. Adding the column fixes that.
 *
 *   - All 15 application tables hold tenant data (conversations, contacts, invoices,
 *     RAG vectors, etc.). With service-role queries the gateway is the only thing
 *     stopping cross-tenant access. RLS is the defense-in-depth layer: even if a
 *     gateway authz bug ships, the database itself refuses cross-tenant reads/writes
 *     on any anon-key/JWT path.
 *
 *   - Service role bypasses RLS by design, so the gateway's existing 26 service-role
 *     call sites keep working unchanged. RLS only kicks in for anon-key + JWT clients
 *     (the dashboard once it ever queries Supabase directly, or any tenant-key tools
 *     we might add later).
 */
import { createChildLogger } from '../../utils/logger.js';
const log = createChildLogger('db:migration:007');
const TENANT_TABLES_WITH_DIRECT_ID = [
    'customers',
    'messages',
    'usage',
    'documents',
    'document_chunks',
    'invoices',
    'budget_config',
    'cron_jobs',
    'cron_executions',
    'sessions',
    'approval_requests',
    'customer_memories',
    'zatca_invoices',
];
const MIGRATION_SQL = `
-- ─── 1. Add ownership column to tenants ────────────────────────────
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS owner_id UUID REFERENCES auth.users(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_tenants_owner ON tenants(owner_id);

-- Backfill: pre-007 onboarding stuffed user.id into settings.owner_id as a fallback.
-- Promote any settings.owner_id values into the new column for existing rows.
UPDATE tenants
SET owner_id = (settings->>'owner_id')::uuid
WHERE owner_id IS NULL
  AND settings ? 'owner_id'
  AND (settings->>'owner_id') ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$';

-- ─── 2. Helper: does the current auth.uid() own this tenant? ──────────
-- SECURITY DEFINER so the lookup against tenants happens without recursing
-- into the very RLS policy we're using it in. STABLE because it only reads.
CREATE OR REPLACE FUNCTION public.auth_owns_tenant(target_tenant_id UUID)
RETURNS BOOLEAN
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
  SELECT EXISTS (
    SELECT 1
    FROM public.tenants t
    WHERE t.id = target_tenant_id
      AND t.owner_id = auth.uid()
  );
$$;

REVOKE ALL ON FUNCTION public.auth_owns_tenant(UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.auth_owns_tenant(UUID) TO authenticated, anon, service_role;

-- ─── 3. Enable RLS on every tenant-scoped table ─────────────────────
ALTER TABLE tenants            ENABLE ROW LEVEL SECURITY;
ALTER TABLE customers          ENABLE ROW LEVEL SECURITY;
ALTER TABLE messages           ENABLE ROW LEVEL SECURITY;
ALTER TABLE usage              ENABLE ROW LEVEL SECURITY;
ALTER TABLE documents          ENABLE ROW LEVEL SECURITY;
ALTER TABLE document_chunks    ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices           ENABLE ROW LEVEL SECURITY;
ALTER TABLE budget_config      ENABLE ROW LEVEL SECURITY;
ALTER TABLE cron_jobs          ENABLE ROW LEVEL SECURITY;
ALTER TABLE cron_executions    ENABLE ROW LEVEL SECURITY;
ALTER TABLE sessions           ENABLE ROW LEVEL SECURITY;
ALTER TABLE session_messages   ENABLE ROW LEVEL SECURITY;
ALTER TABLE approval_requests  ENABLE ROW LEVEL SECURITY;
ALTER TABLE customer_memories  ENABLE ROW LEVEL SECURITY;
ALTER TABLE zatca_invoices     ENABLE ROW LEVEL SECURITY;

-- ─── 4. Policies ─────────────────────────────────────────────────────
-- Pattern: one ALL-verbs policy per table, named tenant_isolation.
-- Service role bypasses RLS, so the gateway's service-role client is unaffected.
-- DROP-then-CREATE makes this re-runnable on existing deployments.

-- tenants: owner can see/manage their own row
DROP POLICY IF EXISTS tenant_owner_only ON tenants;
CREATE POLICY tenant_owner_only ON tenants
  FOR ALL TO authenticated
  USING (owner_id = auth.uid())
  WITH CHECK (owner_id = auth.uid());

-- 13 tables with a direct tenant_id column
${TENANT_TABLES_WITH_DIRECT_ID.map((table) => `DROP POLICY IF EXISTS tenant_isolation ON ${table};
CREATE POLICY tenant_isolation ON ${table}
  FOR ALL TO authenticated
  USING (public.auth_owns_tenant(tenant_id))
  WITH CHECK (public.auth_owns_tenant(tenant_id));`).join('\n\n')}

-- session_messages: no direct tenant_id, joins through sessions
DROP POLICY IF EXISTS tenant_isolation ON session_messages;
CREATE POLICY tenant_isolation ON session_messages
  FOR ALL TO authenticated
  USING (
    public.auth_owns_tenant(
      (SELECT s.tenant_id FROM sessions s WHERE s.id = session_messages.session_id)
    )
  )
  WITH CHECK (
    public.auth_owns_tenant(
      (SELECT s.tenant_id FROM sessions s WHERE s.id = session_messages.session_id)
    )
  );
`;
export async function migrate() {
    log.info('Running migration 007: tenants.owner_id + RLS on all 15 tables');
    log.info('Run the following SQL in Supabase SQL Editor:');
    log.info(MIGRATION_SQL);
}
export async function rollback() {
    const tables = ['tenants', ...TENANT_TABLES_WITH_DIRECT_ID, 'session_messages'];
    const sql = `
${tables.map((t) => `ALTER TABLE ${t} DISABLE ROW LEVEL SECURITY;`).join('\n')}
${tables
        .map((t) => `DROP POLICY IF EXISTS ${t === 'tenants' ? 'tenant_owner_only' : 'tenant_isolation'} ON ${t};`)
        .join('\n')}
DROP FUNCTION IF EXISTS public.auth_owns_tenant(UUID);
ALTER TABLE tenants DROP COLUMN IF EXISTS owner_id;
`;
    log.info('Rollback SQL:');
    log.info(sql);
}
export { MIGRATION_SQL };
//# sourceMappingURL=007-add-owner-id-and-rls.js.map