Backend Agent Skill
Overview
The Backend Agent is responsible for all server-side work in Unite-Hub:
- Next.js API route development (serverless functions)
- Supabase database operations (queries, mutations, RLS)
- Authentication and authorization (NextAuth.js, Supabase Auth)
- Third-party integrations (Gmail API, Stripe, Claude AI)
- Database schema management (migrations, indexes)
- API security and performance (rate limiting, caching)
How to Use This Agent
Trigger
User says: "Create new API endpoint", "Fix database query", "Update RLS policies", "Implement Gmail integration"
What the Agent Does
1. Understand the Request
Questions to Ask:
- What's the API endpoint purpose?
- What database tables are involved?
- What's the expected input/output format?
- What authentication is required?
- What's the priority (P0/P1/P2)?
2. Analyze Current Implementation
Step A: Locate Files
# Find API routes
find src/app/api -name "route.ts" | grep -i "contacts"
# Find database utilities
find src/lib -name "*.ts" | grep -i "db"
Step B: Read Current Code
// Use text_editor tool
text_editor.view("src/app/api/contacts/route.ts")
text_editor.view("src/lib/db.ts")
Step C: Identify Dependencies
- What database tables are queried?
- What authentication is required?
- What external APIs are called?
- What error handling exists?
3. Implement Changes
Step A: Create API Route
All API routes in Unite-Hub follow this pattern:
// src/app/api/example/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createClient } from "@/lib/supabase";
export async function POST(request: NextRequest) {
try {
// 1. Parse request body
const body = await request.json();
const { workspaceId, action, ...params } = body;
// 2. Validate input
if (!workspaceId) {
return NextResponse.json(
{ error: "workspaceId is required" },
{ status: 400 }
);
}
// 3. Get Supabase client
const supabase = createClient();
// 4. Check authentication (if needed)
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}
// 5. Perform database operation
const { data, error } = await supabase
.from("contacts")
.select("*")
.eq("workspace_id", workspaceId) // CRITICAL: Workspace filter
.eq("organization_id", user.organization_id); // CRITICAL: Org filter
if (error) {
console.error("Database error:", error);
return NextResponse.json(
{ error: "Database query failed" },
{ status: 500 }
);
}
// 6. Return success response
return NextResponse.json({
success: true,
data,
count: data.length
});
} catch (error) {
console.error("API error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
// Support OPTIONS for CORS
export async function OPTIONS(request: NextRequest) {
return NextResponse.json({}, { status: 200 });
}
Step B: Database Operations
All database operations MUST use workspace filtering:
// ❌ BAD - No workspace filter
const { data } = await supabase
.from("contacts")
.select("*");
// ✅ GOOD - Workspace filtered
const { data } = await supabase
.from("contacts")
.select("*")
.eq("workspace_id", workspaceId)
.eq("organization_id", orgId);
Required filters for data isolation:
.eq("workspace_id", workspaceId)- Workspace scope.eq("organization_id", orgId)- Organization scope (top-level)
Step C: Update src/lib/db.ts Wrapper
The db.ts wrapper provides consistent database access:
// src/lib/db.ts
import { createClient } from "@/lib/supabase";
export const db = {
contacts: {
async listByWorkspace(workspaceId: string) {
const supabase = createClient();
const { data, error } = await supabase
.from("contacts")
.select("*")
.eq("workspace_id", workspaceId)
.order("created_at", { ascending: false });
if (error) throw error;
return data || [];
},
async getById(contactId: string, workspaceId: string) {
const supabase = createClient();
const { data, error } = await supabase
.from("contacts")
.select("*")
.eq("id", contactId)
.eq("workspace_id", workspaceId)
.single();
if (error) throw error;
return data;
},
async create(contact: ContactInput, workspaceId: string) {
const supabase = createClient();
const { data, error } = await supabase
.from("contacts")
.insert([{ ...contact, workspace_id: workspaceId }])
.select()
.single();
if (error) throw error;
return data;
},
async update(contactId: string, updates: Partial<ContactInput>, workspaceId: string) {
const supabase = createClient();
const { data, error } = await supabase
.from("contacts")
.update(updates)
.eq("id", contactId)
.eq("workspace_id", workspaceId)
.select()
.single();
if (error) throw error;
return data;
}
},
// Similar patterns for campaigns, emails, etc.
};
CRITICAL FIX for V1: Add missing import in src/lib/db.ts:58
// Line 1 - Add import
import { createClient, getSupabaseServer } from "./supabase";
// Line 58 - Fix usage
const supabaseServer = getSupabaseServer();
const { data: workspace, error } = await supabaseServer
.from("workspaces")
.select("*")
.eq("id", workspaceId)
.single();
4. Implement Authentication
Pattern 1: Client-Side Auth (Browser)
import { createClient } from "@/lib/supabase";
export async function GET(request: NextRequest) {
const supabase = createClient();
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// User is authenticated, proceed
}
Pattern 2: Server-Side Auth (API Routes)
import { getSupabaseServer } from "@/lib/supabase";
export async function POST(request: NextRequest) {
const supabase = getSupabaseServer();
const { data: { session }, error } = await supabase.auth.getSession();
if (error || !session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Session is valid, proceed
}
CRITICAL for V1: Re-enable authentication on all API routes
Many routes currently have:
// TODO: Re-enable authentication in production
// const { auth } = await import("@/lib/auth");
// const session = await auth();
Action Required: Remove TODO comments and re-enable auth checks.
5. Row Level Security (RLS) Policies
All Supabase tables MUST have RLS policies:
-- Enable RLS on table
ALTER TABLE contacts ENABLE ROW LEVEL SECURITY;
-- Policy: Users can only see contacts in their workspace
CREATE POLICY "Users can view workspace contacts"
ON contacts
FOR SELECT
USING (
workspace_id IN (
SELECT w.id
FROM workspaces w
JOIN user_organizations uo ON uo.organization_id = w.organization_id
WHERE uo.user_id = auth.uid()
)
);
-- Policy: Users can insert contacts in their workspace
CREATE POLICY "Users can create workspace contacts"
ON contacts
FOR INSERT
WITH CHECK (
workspace_id IN (
SELECT w.id
FROM workspaces w
JOIN user_organizations uo ON uo.organization_id = w.organization_id
WHERE uo.user_id = auth.uid()
)
);
-- Policy: Users can update contacts in their workspace
CREATE POLICY "Users can update workspace contacts"
ON contacts
FOR UPDATE
USING (
workspace_id IN (
SELECT w.id
FROM wor