Backend Architecture
PIP AI uses a lightweight backend powered by Nuxt Nitro server routes and Supabase as the primary backend service.
Server Routes
All server routes are defined in server/api/ and follow Nitro conventions:
Admin Routes
| Route | Method | Handler | Purpose |
|---|---|---|---|
/api/admin/specs/upload | POST | upload.post.ts | Upload spec document and trigger processing |
/api/admin/specs/callback | POST | callback.post.ts | N8N callback after spec processing |
/api/admin/specs/retry | POST | retry.post.ts | Retry failed spec processing |
/api/admin/brands | GET | index.get.ts | List all brands |
/api/admin/brands | POST | index.post.ts | Create a new brand |
/api/admin/areas | GET | index.get.ts | List all areas |
/api/admin/areas | POST | index.post.ts | Create a new area |
Export Routes
| Route | Method | Handler | Purpose |
|---|---|---|---|
/api/export/pdf | POST | pdf.post.ts | Generate PDF export |
/api/export/render/[projectId] | GET | [projectId].get.ts | Render project for export |
/api/export/status/[jobId] | GET | [jobId].get.ts | Check export job status |
Nano Banana (AI Image Generation)
| Route | Method | Handler | Purpose |
|---|---|---|---|
/api/nano-banana/jobs | POST | jobs.post.ts | Create image generation job |
/api/nano-banana/jobs | GET | jobs.get.ts | List jobs for a project |
/api/nano-banana/messages | POST | messages.post.ts | Send refinement message |
/api/nano-banana/jobs/[jobId]/messages | GET | messages.get.ts | Get chat history |
/api/nano-banana/callback | POST | callback.post.ts | N8N callback with results |
Poly Haven (Material Library)
| Route | Method | Handler | Purpose |
|---|---|---|---|
/api/polyhaven/search | GET | search.get.ts | Search Poly Haven textures |
/api/polyhaven/categories | GET | categories.get.ts | Get texture categories |
/api/polyhaven/info/[id] | GET | [id].get.ts | Get asset details |
/api/polyhaven/files/[id] | GET | [id].get.ts | Get asset file URLs |
Authentication in Server Routes
Server routes use the Supabase service role key to bypass RLS:
typescript
import { serverSupabaseServiceRole } from '#supabase/server'
export default defineEventHandler(async (event) => {
const client = await serverSupabaseServiceRole(event)
// Full database access, bypasses RLS
})WARNING
The service role key has full database access. Always validate inputs and check permissions in server route handlers.
Webhook Security
N8N webhook callbacks are authenticated using a shared secret:
typescript
const secret = event.headers.get('x-webhook-secret')
if (secret !== useRuntimeConfig().n8nWebhookSecret) {
throw createError({ statusCode: 401, message: 'Unauthorized' })
}N8N Integration
Server routes trigger N8N workflows via HTTP webhooks:
typescript
// Trigger N8N workflow
await $fetch(`${config.public.n8nBaseUrl}${config.public.n8nWebhookSpecProcessor}`, {
method: 'POST',
headers: { 'x-webhook-secret': config.n8nWebhookSecret },
body: {
spec_upload_id: specUpload.id,
document_id: document.id,
storage_path: document.storage_path,
brand_name: specUpload.brand_name,
area: specUpload.area,
}
})Error Handling
Server routes use Nitro's createError for consistent error responses:
typescript
throw createError({
statusCode: 400,
message: 'Missing required field: projectId'
})