import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' import { environment } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { savePersonalEnvironmentContract } from '@/lib/api/contracts/environment' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { syncPersonalEnvCredentialsForUser } from '@/lib/credentials/environment' import type { EnvironmentVariable } from '@/lib/environment/api' import { captureServerEvent } from '@/lib/posthog/server' const logger = createLogger('EnvironmentAPI') export const POST = withRouteHandler(async (req: NextRequest) => { const requestId = generateRequestId() try { const session = await getSession() if (!session?.user?.id) { logger.warn(`[${requestId}] Unauthorized environment variables update attempt`) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const parsed = await parseRequest( savePersonalEnvironmentContract, req, {}, { validationErrorResponse: (error) => { logger.warn(`[${requestId}] Invalid environment variables data`, { errors: error.issues }) return NextResponse.json( { error: 'Invalid request data', details: error.issues }, { status: 400 } ) }, } ) if (!parsed.success) return parsed.response const { variables } = parsed.data.body const encryptedVariables = await Promise.all( Object.entries(variables).map(async ([key, value]) => { const { encrypted } = await encryptSecret(value) return [key, encrypted] as const }) ).then((entries) => Object.fromEntries(entries)) await db .insert(environment) .values({ id: generateId(), userId: session.user.id, variables: encryptedVariables, updatedAt: new Date(), }) .onConflictDoUpdate({ target: [environment.userId], set: { variables: encryptedVariables, updatedAt: new Date(), }, }) await syncPersonalEnvCredentialsForUser({ userId: session.user.id, envKeys: Object.keys(variables), }) recordAudit({ actorId: session.user.id, actorName: session.user.name, actorEmail: session.user.email, action: AuditAction.ENVIRONMENT_UPDATED, resourceType: AuditResourceType.ENVIRONMENT, resourceId: session.user.id, description: `Updated ${Object.keys(variables).length} personal environment variable(s)`, metadata: { variableCount: Object.keys(variables).length, updatedKeys: Object.keys(variables), scope: 'personal', }, request: req, }) captureServerEvent(session.user.id, 'environment_updated', { key_count: Object.keys(variables).length, scope: 'personal', }) return NextResponse.json({ success: true }) } catch (error) { logger.error(`[${requestId}] Error updating environment variables`, error) return NextResponse.json({ error: 'Failed to update environment variables' }, { status: 500 }) } }) export const GET = withRouteHandler(async (request: Request) => { const requestId = generateRequestId() try { const session = await getSession() if (!session?.user?.id) { logger.warn(`[${requestId}] Unauthorized environment variables access attempt`) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = session.user.id const result = await db .select() .from(environment) .where(eq(environment.userId, userId)) .limit(1) if (!result.length || !result[0].variables) { return NextResponse.json({ data: {} }, { status: 200 }) } const encryptedVariables = result[0].variables as Record const decryptedEntries = await Promise.all( Object.entries(encryptedVariables).map(async ([key, encryptedValue]) => { try { const { decrypted } = await decryptSecret(encryptedValue) return [key, { key, value: decrypted }] as const } catch (error) { logger.error(`[${requestId}] Error decrypting variable ${key}`, error) return [key, { key, value: '' }] as const } }) ) const decryptedVariables = Object.fromEntries(decryptedEntries) as Record< string, EnvironmentVariable > return NextResponse.json({ data: decryptedVariables }, { status: 200 }) } catch (error) { logger.error(`[${requestId}] Environment fetch error`, error) return NextResponse.json({ error: toError(error).message }, { status: 500 }) } })