ef5a84a5d5
Enrich ExtensionContext with supabase, emit(), settings, storage, log, and services so extensions can receive everything through dependency injection instead of importing core modules directly. - Add context factory and inject context into event handlers via registry - Move supplier invoice journal entry creation to core event handler - Add services.ingestTransactions to ExtensionContext for enable-banking - Create catch-all API route for extension-declared apiRoutes - Migrate 5 extensions to accept context with dynamic import fallbacks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import { createClient } from '@/lib/supabase/server'
|
|
import { NextResponse } from 'next/server'
|
|
import { ensureInitialized } from '@/lib/init'
|
|
import { extensionRegistry } from '@/lib/extensions/registry'
|
|
import { createExtensionContext } from '@/lib/extensions/context-factory'
|
|
import { isExtensionEnabled } from '@/lib/extensions/toggle-check'
|
|
|
|
ensureInitialized()
|
|
|
|
/**
|
|
* Catch-all route for extension-declared API routes.
|
|
*
|
|
* URL scheme: /api/extensions/ext/{extensionId}/{...routePath}
|
|
* Example: /api/extensions/ext/enable-banking/banks → GET /banks
|
|
*
|
|
* - Looks up the extension in the registry
|
|
* - Checks the extension toggle (disabled → 403)
|
|
* - Matches method + path to registered apiRoutes
|
|
* - Builds an ExtensionContext and passes it to the handler
|
|
*/
|
|
async function handleRequest(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ path: string[] }> }
|
|
): Promise<Response> {
|
|
const segments = await params
|
|
|
|
if (!segments.path || segments.path.length < 1) {
|
|
return NextResponse.json({ error: 'Invalid extension route' }, { status: 400 })
|
|
}
|
|
|
|
const [extensionId, ...rest] = segments.path
|
|
const routePath = '/' + rest.join('/')
|
|
const method = request.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
|
|
// Look up extension
|
|
const extension = extensionRegistry.get(extensionId)
|
|
if (!extension || !extension.apiRoutes || extension.apiRoutes.length === 0) {
|
|
return NextResponse.json({ error: 'Extension not found' }, { status: 404 })
|
|
}
|
|
|
|
// Auth check
|
|
const supabase = await createClient()
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
|
|
if (!user) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
}
|
|
|
|
// Toggle check — disabled extensions return 403
|
|
const enabled = await isExtensionEnabled(user.id, 'general', extensionId)
|
|
if (!enabled) {
|
|
return NextResponse.json({ error: 'Extension is disabled' }, { status: 403 })
|
|
}
|
|
|
|
// Find matching route
|
|
const route = extension.apiRoutes.find(
|
|
(r) => r.method === method && r.path === routePath
|
|
)
|
|
|
|
if (!route) {
|
|
return NextResponse.json({ error: 'Route not found' }, { status: 404 })
|
|
}
|
|
|
|
// Build context and dispatch
|
|
const ctx = createExtensionContext(supabase, user.id, extensionId)
|
|
return route.handler(request, ctx)
|
|
}
|
|
|
|
export const GET = handleRequest
|
|
export const POST = handleRequest
|
|
export const PUT = handleRequest
|
|
export const DELETE = handleRequest
|
|
export const PATCH = handleRequest
|