feat: agency control panel for per-client connections
Matrix view of Meta/Google/Drive/Clientify, follow/archive clients, and per-channel ON/OFF with account IDs in the client hub. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
86
src/app/api/admin/tenants/route.ts
Normal file
86
src/app/api/admin/tenants/route.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { createClient } from '@/lib/supabase/server';
|
||||
import {
|
||||
isAgencyRole,
|
||||
serviceHeaders,
|
||||
supabaseUrl,
|
||||
userRole,
|
||||
} from '@/lib/agency';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
async function requireAgency() {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { error: NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) };
|
||||
}
|
||||
if (!isAgencyRole(userRole(user))) {
|
||||
return { error: NextResponse.json({ error: 'Forbidden' }, { status: 403 }) };
|
||||
}
|
||||
const headers = serviceHeaders();
|
||||
if (!headers) {
|
||||
return {
|
||||
error: NextResponse.json({ error: 'no service role' }, { status: 503 }),
|
||||
};
|
||||
}
|
||||
return { headers };
|
||||
}
|
||||
|
||||
/** PATCH — follow/unfollow client or set notes. */
|
||||
export async function PATCH(req: NextRequest) {
|
||||
const gate = await requireAgency();
|
||||
if ('error' in gate && gate.error) return gate.error;
|
||||
const { headers } = gate as {
|
||||
headers: { apikey: string; Authorization: string };
|
||||
};
|
||||
|
||||
const body = await req.json().catch(() => ({}));
|
||||
const tenantId =
|
||||
typeof body.tenantId === 'string' ? body.tenantId.trim() : '';
|
||||
if (!tenantId) {
|
||||
return NextResponse.json({ error: 'tenantId requerido' }, { status: 400 });
|
||||
}
|
||||
|
||||
const patch: Record<string, unknown> = {};
|
||||
if (typeof body.followed === 'boolean') {
|
||||
patch.agency_followed = body.followed;
|
||||
}
|
||||
if (typeof body.notes === 'string') {
|
||||
patch.agency_notes = body.notes.trim() || null;
|
||||
}
|
||||
if (!Object.keys(patch).length) {
|
||||
return NextResponse.json(
|
||||
{ error: 'followed o notes requeridos' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const r = await fetch(
|
||||
`${supabaseUrl()}/rest/v1/tenants?id=eq.${encodeURIComponent(tenantId)}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
Prefer: 'return=representation',
|
||||
},
|
||||
body: JSON.stringify(patch),
|
||||
}
|
||||
);
|
||||
if (!r.ok) {
|
||||
const msg = await r.text();
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: msg.includes('agency_followed')
|
||||
? 'Aplicar sql/agency_control.sql'
|
||||
: msg.slice(0, 300),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
const rows = await r.json();
|
||||
return NextResponse.json({ tenant: rows[0] || rows });
|
||||
}
|
||||
Reference in New Issue
Block a user