| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import { generateText, Output, stepCountIs } from 'ai'
- import { IS_PLATFORM } from 'common'
- import { source } from 'common-tags'
- import { NextApiRequest, NextApiResponse } from 'next'
- import { z } from 'zod'
- import type { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
- import { getOrgAIDetails } from '@/lib/ai/ai-details'
- import { getModel } from '@/lib/ai/model'
- import { DEFAULT_COMPLETION_MODEL } from '@/lib/ai/model.utils'
- import { RLS_PROMPT } from '@/lib/ai/prompts'
- import { getTools } from '@/lib/ai/tools'
- import apiWrapper from '@/lib/api/apiWrapper'
- const policySchema = z.object({
- sql: z.string().describe('The generated Postgres CREATE POLICY statement.'),
- name: z.string().describe('The name of the policy.'),
- command: z
- .enum(['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'ALL'])
- .describe('The SQL command this policy applies to.'),
- definition: z
- .string()
- .optional()
- .describe('The USING clause expression (for SELECT, UPDATE, DELETE).'),
- check: z.string().optional().describe('The WITH CHECK clause expression (for INSERT, UPDATE).'),
- action: z
- .enum(['PERMISSIVE', 'RESTRICTIVE'])
- .default('PERMISSIVE')
- .describe('Whether the policy is PERMISSIVE or RESTRICTIVE.'),
- roles: z.array(z.string()).default(['public']).describe('The roles this policy applies to.'),
- })
- const requestBodySchema = z.object({
- tableName: z.string().min(1),
- schema: z.string().default('public'),
- columns: z.array(z.string()).optional(),
- projectRef: z.string().min(1),
- connectionString: z.string().min(1),
- orgSlug: z.string().optional(),
- message: z.string().optional(),
- })
- async function handler(req: NextApiRequest, res: NextApiResponse) {
- const { method } = req
- switch (method) {
- case 'POST':
- return handlePost(req, res)
- default:
- res.setHeader('Allow', ['POST'])
- res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
- }
- }
- export async function handlePost(req: NextApiRequest, res: NextApiResponse) {
- const authorization = req.headers.authorization
- const accessToken = authorization?.replace('Bearer ', '')
- if (IS_PLATFORM && !accessToken) {
- return res.status(401).json({ error: 'Authorization token is required' })
- }
- const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
- const { data, error: parseError } = requestBodySchema.safeParse(body)
- if (parseError) {
- return res.status(400).json({ error: 'Invalid request body', issues: parseError.issues })
- }
- const { tableName, schema, columns = [], projectRef, connectionString, orgSlug, message } = data
- let aiOptInLevel: AiOptInLevel = 'disabled'
- if (!IS_PLATFORM) {
- aiOptInLevel = 'schema'
- }
- if (IS_PLATFORM && orgSlug && authorization) {
- try {
- const { aiOptInLevel: orgAIOptInLevel } = await getOrgAIDetails({
- orgSlug,
- authorization,
- })
- aiOptInLevel = orgAIOptInLevel
- } catch (error) {
- return res.status(400).json({
- error: 'There was an error fetching your organization details',
- })
- }
- }
- try {
- const { modelParams, error: modelError } = await getModel({
- provider: 'openai',
- modelEntry: DEFAULT_COMPLETION_MODEL,
- })
- if (modelError) {
- return res.status(500).json({ error: modelError.message })
- }
- const tools = await getTools({
- projectRef,
- connectionString,
- authorization,
- aiOptInLevel,
- accessToken,
- })
- const { experimental_output } = await generateText({
- ...modelParams,
- stopWhen: stepCountIs(5),
- prompt: source`
- You are a Postgres RLS (Row Level Security) expert.
- Determine the most appropriate policies for the "${schema}"."${tableName}" table within a Briven project.
- ${columns.length > 0 ? `Table columns: ${columns.join(', ')}` : 'No column metadata provided.'}
- ${message ? `User request: ${message}` : ''}
- RLS Guide: ${RLS_PROMPT}
- Requirements:
- - Use the available planning and schema tools (like "list_policies" or "list_tables") to inspect the "${schema}" schema and existing policies before generating new ones.
- - Ensure policies strictly adhere to the existing schema
- - Return a curated list of recommended CREATE POLICY statements as JSON.
- - Each policy must include: name, sql, command (SELECT/INSERT/UPDATE/DELETE/ALL), action (PERMISSIVE/RESTRICTIVE), roles (array of role names).
- - Include "definition" (USING clause expression without the USING keyword) for SELECT, UPDATE, DELETE policies.
- - Include "check" (WITH CHECK clause expression without the WITH CHECK keywords) for INSERT, UPDATE policies.
- - Avoid duplicating existing policies and reference the public schema and typical Briven best practices when deciding the coverage.
- - Prefer PERMISSIVE policies unless a RESTRICTIVE policy is explicitly required
- `,
- tools,
- experimental_output: Output.object({
- schema: z.object({
- policies: z.array(policySchema),
- }),
- }),
- })
- // Add table and schema to each policy from the request
- const policies = (experimental_output?.policies ?? []).map((policy) => ({
- ...policy,
- table: tableName,
- schema,
- }))
- return res.json(policies)
- } catch (error) {
- if (error instanceof Error) {
- console.error(`AI policy generation failed: ${error.message}`)
- return res.status(500).json({
- error: 'Failed to generate policy. Please try again.',
- })
- }
- return res.status(500).json({
- error: 'An unknown error occurred.',
- })
- }
- }
- const wrapper = (req: NextApiRequest, res: NextApiResponse) =>
- apiWrapper(req, res, handler, { withAuth: true })
- export default wrapper
|