mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 17:29:45 +00:00
Migration 017 made domain tables account-scoped: account_id is NOT NULL and INSERT RLS is WITH CHECK (is_account_member(account_id, ...)). Several client- and server-side create flows still inserted only user_id, so on a fresh install they failed with 403 / "new row violates row-level security policy" / not-null violations. Resolve account_id from useAuth() (client) or the caller's profile (server) and include it on every account-scoped insert: - contacts: contact-form, import-modal, broadcast CSV upsert - contact_notes: contact-sidebar, contact-detail-view - tags: tag-manager - broadcasts: broadcasts/new page, use-broadcast-sending - deals: deal-form - automations: POST /api/automations, POST /api/automations/[id]/duplicate Each path also guards on a missing accountId with a clear message instead of letting the insert fail opaquely. pipelines/page.tsx and api/flows/route.ts are intentionally left to the already-open PR #193, which fixes those same paths (and adds tests). Verified: a full sweep of every account-scoped insert/upsert across src/ now includes account_id (except the two paths owned by #193). typecheck, lint (0 errors), and 370 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import { createClient } from '@/lib/supabase/server'
|
|
import { supabaseAdmin } from '@/lib/automations/admin-client'
|
|
import { getTemplate } from '@/lib/automations/templates'
|
|
import { insertSteps, type BuilderStepInput } from '@/lib/automations/steps-tree'
|
|
import {
|
|
validateStepsForActivation,
|
|
validateTriggerForActivation,
|
|
} from '@/lib/automations/validate'
|
|
|
|
export async function GET() {
|
|
const supabase = await createClient()
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser()
|
|
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
|
|
const { data, error } = await supabase
|
|
.from('automations')
|
|
.select('*')
|
|
.order('created_at', { ascending: false })
|
|
if (error) return NextResponse.json({ error: error.message }, { status: 500 })
|
|
return NextResponse.json({ automations: data ?? [] })
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const supabase = await createClient()
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser()
|
|
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
|
|
// Resolve the caller's account_id — `automations.account_id` is NOT
|
|
// NULL post-017, so an INSERT without it trips the not-null constraint
|
|
// even though the admin client bypasses RLS.
|
|
const { data: profile } = await supabase
|
|
.from('profiles')
|
|
.select('account_id')
|
|
.eq('user_id', user.id)
|
|
.single()
|
|
const accountId = profile?.account_id as string | undefined
|
|
if (!accountId) {
|
|
return NextResponse.json(
|
|
{ error: 'Your profile is not linked to an account.' },
|
|
{ status: 403 },
|
|
)
|
|
}
|
|
|
|
const body = await request.json().catch(() => null)
|
|
if (!body) return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
|
|
|
|
const { name, description, trigger_type, trigger_config, is_active, steps, template } = body
|
|
|
|
let effectiveSteps: BuilderStepInput[] | undefined = steps
|
|
let effectiveName = name
|
|
let effectiveDescription = description
|
|
let effectiveTriggerType = trigger_type
|
|
let effectiveTriggerConfig = trigger_config
|
|
|
|
if (template && (!steps || steps.length === 0)) {
|
|
const t = getTemplate(template)
|
|
if (t) {
|
|
effectiveName = effectiveName ?? t.name
|
|
effectiveDescription = effectiveDescription ?? t.description
|
|
effectiveTriggerType = effectiveTriggerType ?? t.trigger_type
|
|
effectiveTriggerConfig = effectiveTriggerConfig ?? t.trigger_config
|
|
effectiveSteps = t.steps as unknown as BuilderStepInput[]
|
|
}
|
|
}
|
|
|
|
if (!effectiveName || !effectiveTriggerType) {
|
|
return NextResponse.json(
|
|
{ error: 'name and trigger_type are required' },
|
|
{ status: 400 },
|
|
)
|
|
}
|
|
|
|
// Block activation of a clearly broken automation up-front instead of
|
|
// letting every trigger silently produce a failed log row. Drafts
|
|
// (is_active=false) are allowed to be incomplete so users can save
|
|
// progress mid-build.
|
|
if (is_active) {
|
|
const issues = [
|
|
...validateTriggerForActivation(effectiveTriggerType, effectiveTriggerConfig ?? {}),
|
|
...validateStepsForActivation(
|
|
(effectiveSteps ?? []) as unknown as { step_type: string; step_config: Record<string, unknown> }[],
|
|
),
|
|
]
|
|
if (issues.length > 0) {
|
|
return NextResponse.json(
|
|
{ error: 'Cannot activate automation with invalid configuration', issues },
|
|
{ status: 400 },
|
|
)
|
|
}
|
|
}
|
|
|
|
const admin = supabaseAdmin()
|
|
const { data: automation, error: insertErr } = await admin
|
|
.from('automations')
|
|
.insert({
|
|
user_id: user.id,
|
|
account_id: accountId,
|
|
name: effectiveName,
|
|
description: effectiveDescription ?? null,
|
|
trigger_type: effectiveTriggerType,
|
|
trigger_config: effectiveTriggerConfig ?? {},
|
|
is_active: !!is_active,
|
|
})
|
|
.select()
|
|
.single()
|
|
|
|
if (insertErr || !automation) {
|
|
return NextResponse.json(
|
|
{ error: insertErr?.message ?? 'insert failed' },
|
|
{ status: 500 },
|
|
)
|
|
}
|
|
|
|
if (effectiveSteps && effectiveSteps.length > 0) {
|
|
const err = await insertSteps(automation.id, effectiveSteps)
|
|
if (err) return NextResponse.json({ error: err }, { status: 500 })
|
|
}
|
|
|
|
return NextResponse.json({ automation }, { status: 201 })
|
|
}
|