From 9f9b4dd0fee472107a581fe8c1688a8f9f3b7760 Mon Sep 17 00:00:00 2001 From: Alex Patterson Date: Thu, 5 Mar 2026 01:11:04 -0500 Subject: [PATCH] feat: migrate video services to Sanity config system (Phase B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remotion.ts: awsRegion, serveUrl, functionName → getConfigValue('remotion_config') - elevenlabs.ts: voiceId → getConfigValue('pipeline_config', 'elevenLabsVoiceId') - gcs.ts: bucketName, projectId → getConfigValue('gcs_config') - All use env var fallbacks for migration safety - Secrets (API keys, AWS credentials) remain as process.env --- lib/services/elevenlabs.ts | 19 ++++++++----------- lib/services/gcs.ts | 13 +++++++------ lib/services/remotion.ts | 21 +++++++++++---------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/services/elevenlabs.ts b/lib/services/elevenlabs.ts index faae42f3..c9e7202a 100644 --- a/lib/services/elevenlabs.ts +++ b/lib/services/elevenlabs.ts @@ -12,6 +12,7 @@ import { type WordTimestamp, type SceneAudioResult, } from "@/lib/utils/audio-timestamps"; +import { getConfigValue } from "@/lib/config"; const ELEVENLABS_API_BASE = "https://api.elevenlabs.io/v1"; @@ -62,9 +63,12 @@ interface TTSWithTimestampsResponse { * @returns The resolved {@link ElevenLabsConfig}. * @throws {Error} If required environment variables are missing. */ -function getConfig(): ElevenLabsConfig { +async function getElevenLabsConfig(): Promise { const apiKey = process.env.ELEVENLABS_API_KEY; - const voiceId = process.env.ELEVENLABS_VOICE_ID; + const voiceId = await getConfigValue( + "pipeline_config", "elevenLabsVoiceId", + process.env.ELEVENLABS_VOICE_ID || "pNInz6obpgDQGcFmaJgB" + ); if (!apiKey) { throw new Error( @@ -73,13 +77,6 @@ function getConfig(): ElevenLabsConfig { ); } - if (!voiceId) { - throw new Error( - "Missing ELEVENLABS_VOICE_ID environment variable. " + - "Set it in your .env.local or deployment environment." - ); - } - return { apiKey, voiceId }; } @@ -106,7 +103,7 @@ export async function generateSpeech(text: string): Promise { throw new Error("Cannot generate speech from empty text."); } - const { apiKey, voiceId } = getConfig(); + const { apiKey, voiceId } = await getElevenLabsConfig(); const url = `${ELEVENLABS_API_BASE}/text-to-speech/${voiceId}`; @@ -244,7 +241,7 @@ export async function generateSpeechWithTimestamps( throw new Error("Cannot generate speech from empty text."); } - const { apiKey, voiceId } = getConfig(); + const { apiKey, voiceId } = await getElevenLabsConfig(); const url = `${ELEVENLABS_API_BASE}/text-to-speech/${voiceId}/with-timestamps`; diff --git a/lib/services/gcs.ts b/lib/services/gcs.ts index 64d2f94e..8709cc40 100644 --- a/lib/services/gcs.ts +++ b/lib/services/gcs.ts @@ -14,6 +14,7 @@ */ import * as crypto from "crypto"; +import { getConfigValue } from "@/lib/config"; // --------------------------------------------------------------------------- // Types @@ -63,9 +64,9 @@ const TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000; // 5 minutes * The private key may contain literal `\\n` sequences from the env var; * these are converted to real newline characters. */ -export function getGCSConfig(): GCSConfig { - const bucket = process.env.GCS_BUCKET; - const projectId = process.env.GCS_PROJECT_ID; +export async function getGCSConfig(): Promise { + const bucket = await getConfigValue("gcs_config", "bucketName", process.env.GCS_BUCKET); + const projectId = await getConfigValue("gcs_config", "projectId", process.env.GCS_PROJECT_ID); const clientEmail = process.env.GCS_CLIENT_EMAIL; let privateKey = process.env.GCS_PRIVATE_KEY; @@ -205,7 +206,7 @@ async function getAccessToken(): Promise { return cachedToken.accessToken; } - const config = getGCSConfig(); + const config = await getGCSConfig(); const jwt = createServiceAccountJWT(config); cachedToken = await exchangeJWTForToken(jwt); return cachedToken.accessToken; @@ -231,7 +232,7 @@ export async function uploadToGCS( path: string, contentType: string ): Promise { - const config = getGCSConfig(); + const config = await getGCSConfig(); const token = await getAccessToken(); const encodedPath = encodeURIComponent(path); @@ -338,7 +339,7 @@ export async function getSignedUrl( expiresInMinutes = 60 ): Promise { // We still validate config to fail fast if env vars are missing - const config = getGCSConfig(); + const config = await getGCSConfig(); // For public objects, the public URL is sufficient void expiresInMinutes; // acknowledged but unused for public objects diff --git a/lib/services/remotion.ts b/lib/services/remotion.ts index feca1b06..8cb72b54 100644 --- a/lib/services/remotion.ts +++ b/lib/services/remotion.ts @@ -22,12 +22,13 @@ import { getRenderProgress, type AwsRegion, } from "@remotion/lambda/client"; +import { getConfigValue } from "@/lib/config"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- -export interface RemotionConfig { +export interface RemotionLambdaConfig { awsAccessKeyId: string; awsSecretAccessKey: string; region: string; @@ -130,11 +131,11 @@ function mapInputProps(input: RenderInput): Record { * Get Remotion Lambda configuration from environment variables. * Throws if any required env var is missing. */ -export function getRemotionConfig(): RemotionConfig { +export async function getRemotionConfig(): Promise { const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID; const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY; - const region = process.env.REMOTION_AWS_REGION; - const serveUrl = process.env.REMOTION_SERVE_URL; + const region = await getConfigValue("remotion_config", "awsRegion", process.env.REMOTION_AWS_REGION); + const serveUrl = await getConfigValue("remotion_config", "serveUrl", process.env.REMOTION_SERVE_URL); const missing: string[] = []; if (!awsAccessKeyId) missing.push("AWS_ACCESS_KEY_ID"); @@ -161,8 +162,8 @@ export function getRemotionConfig(): RemotionConfig { /** * Get the Lambda function name from env or use the default. */ -function getFunctionName(): string { - return process.env.REMOTION_FUNCTION_NAME || DEFAULT_FUNCTION_NAME; +async function getFunctionName(): Promise { + return getConfigValue("remotion_config", "functionName", process.env.REMOTION_FUNCTION_NAME || DEFAULT_FUNCTION_NAME); } // --------------------------------------------------------------------------- @@ -202,8 +203,8 @@ async function startRender( composition: string, input: RenderInput ): Promise<{ renderId: string; bucketName: string }> { - const config = getRemotionConfig(); - const functionName = getFunctionName(); + const config = await getRemotionConfig(); + const functionName = await getFunctionName(); const region = config.region as AwsRegion; log(`Starting render for composition "${composition}"`, { @@ -291,8 +292,8 @@ export async function checkRenderProgress( renderId: string, bucketName: string ): Promise { - const config = getRemotionConfig(); - const functionName = getFunctionName(); + const config = await getRemotionConfig(); + const functionName = await getFunctionName(); const region = config.region as AwsRegion; const progress = await getRenderProgress({