updateAreaLead used .single(), which errors (raw PostgREST message) rather than returning null when the id isn't in the caller's space. Switch to .maybeSingle() so the existing 'Area lead not found' guard is reached, matching deleteAreaLead. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
158 lines
5.1 KiB
TypeScript
158 lines
5.1 KiB
TypeScript
'use server'
|
|
|
|
import { revalidatePath } from 'next/cache'
|
|
import { createClient } from '@/lib/supabase/server'
|
|
import { requireMemberWithRole, parseInput, logActivity } from '@/lib/auth-helpers'
|
|
import { ADMIN_ROLES } from '@/lib/permissions'
|
|
import { upsertAreaLeadSchema, createAreaLeadSchema, updateAreaLeadSchema, uuidSchema } from '@/lib/validations'
|
|
|
|
export async function upsertAreaLead(formData: {
|
|
area_code: string
|
|
area_name: string
|
|
lead_id?: string
|
|
lead_handle?: string
|
|
status?: string
|
|
}) {
|
|
const v = parseInput(upsertAreaLeadSchema, formData)
|
|
if (!v.ok) return { error: v.error }
|
|
|
|
// area_code is the (space_id, area_code) upsert conflict target. Postgres
|
|
// treats NULLs as distinct in unique constraints, so a null/empty code
|
|
// would let duplicate area-lead rows accumulate. Require it.
|
|
if (!v.data.area_code || !v.data.area_code.trim()) {
|
|
return { error: 'An area code is required.' }
|
|
}
|
|
|
|
const supabase = await createClient()
|
|
const auth = await requireMemberWithRole(supabase, ADMIN_ROLES, 'Admin access required')
|
|
if (!auth.ok) return { error: auth.error }
|
|
const { member } = auth
|
|
|
|
// If a lead member is referenced, confirm they belong to this space (the
|
|
// row is space-scoped and RLS scopes reads, but reject a foreign id at the
|
|
// boundary for consistency with the rest of the codebase).
|
|
if (v.data.lead_id) {
|
|
const { data: lead } = await supabase
|
|
.from('space_members')
|
|
.select('id')
|
|
.eq('id', v.data.lead_id)
|
|
.eq('space_id', member.space_id)
|
|
.maybeSingle()
|
|
if (!lead) return { error: 'The selected lead is not a member of this space.' }
|
|
}
|
|
|
|
const { data, error } = await supabase
|
|
.from('area_leads')
|
|
.upsert(
|
|
{
|
|
space_id: member.space_id,
|
|
area_code: v.data.area_code,
|
|
area_name: v.data.area_name,
|
|
lead_id: v.data.lead_id ?? null,
|
|
lead_handle: v.data.lead_handle ?? null,
|
|
status: v.data.status,
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
{ onConflict: 'space_id,area_code' },
|
|
)
|
|
.select()
|
|
.single()
|
|
|
|
if (error) return { error: error.message }
|
|
revalidatePath('/ops')
|
|
return { data }
|
|
}
|
|
|
|
// ─── Ops area-lead roster (area_name-keyed) ──────────────────────────────────
|
|
// The Ops UI manages a plain roster of "who leads which area". These are a
|
|
// separate concern from upsertAreaLead's area_code-keyed role interface above;
|
|
// area_code is left null here.
|
|
|
|
export async function createAreaLead(formData: {
|
|
area_name: string
|
|
lead_handle: string
|
|
description?: string | null
|
|
}) {
|
|
const v = parseInput(createAreaLeadSchema, formData)
|
|
if (!v.ok) return { error: v.error }
|
|
|
|
const supabase = await createClient()
|
|
const auth = await requireMemberWithRole(supabase, ADMIN_ROLES, 'Admin access required')
|
|
if (!auth.ok) return { error: auth.error }
|
|
const { member } = auth
|
|
|
|
const { data, error } = await supabase
|
|
.from('area_leads')
|
|
.insert({
|
|
space_id: member.space_id,
|
|
area_name: v.data.area_name,
|
|
lead_handle: v.data.lead_handle,
|
|
description: v.data.description ?? null,
|
|
})
|
|
.select()
|
|
.single()
|
|
|
|
if (error) return { error: error.message }
|
|
|
|
await logActivity(supabase, member, 'created', 'area_lead', data.id as string)
|
|
revalidatePath('/ops')
|
|
return { data }
|
|
}
|
|
|
|
export async function updateAreaLead(
|
|
id: string,
|
|
updates: { area_name: string; lead_handle: string; description?: string | null },
|
|
) {
|
|
const v = parseInput(updateAreaLeadSchema, { id, ...updates })
|
|
if (!v.ok) return { error: v.error }
|
|
|
|
const supabase = await createClient()
|
|
const auth = await requireMemberWithRole(supabase, ADMIN_ROLES, 'Admin access required')
|
|
if (!auth.ok) return { error: auth.error }
|
|
const { member } = auth
|
|
|
|
const { data, error } = await supabase
|
|
.from('area_leads')
|
|
.update({
|
|
area_name: v.data.area_name,
|
|
lead_handle: v.data.lead_handle,
|
|
description: v.data.description ?? null,
|
|
})
|
|
.eq('id', v.data.id)
|
|
.eq('space_id', member.space_id)
|
|
.select()
|
|
.maybeSingle()
|
|
|
|
if (error) return { error: error.message }
|
|
// A cross-space / missing id matches 0 rows; surface the friendly message
|
|
// (maybeSingle returns null rather than erroring, unlike single()).
|
|
if (!data) return { error: 'Area lead not found' }
|
|
|
|
await logActivity(supabase, member, 'updated', 'area_lead', v.data.id)
|
|
revalidatePath('/ops')
|
|
return { data }
|
|
}
|
|
|
|
export async function deleteAreaLead(id: string) {
|
|
const v = parseInput(uuidSchema, id)
|
|
if (!v.ok) return { error: 'Invalid area lead ID' }
|
|
|
|
const supabase = await createClient()
|
|
const auth = await requireMemberWithRole(supabase, ADMIN_ROLES, 'Admin access required')
|
|
if (!auth.ok) return { error: auth.error }
|
|
const { member } = auth
|
|
|
|
const { error, count } = await supabase
|
|
.from('area_leads')
|
|
.delete({ count: 'exact' })
|
|
.eq('id', v.data)
|
|
.eq('space_id', member.space_id)
|
|
|
|
if (error) return { error: error.message }
|
|
if (count === 0) return { error: 'Area lead not found' }
|
|
|
|
await logActivity(supabase, member, 'deleted', 'area_lead', v.data)
|
|
revalidatePath('/ops')
|
|
return { success: true as const }
|
|
}
|