From da629b3ecde7c429f58d7a7a65bb46e627b064a2 Mon Sep 17 00:00:00 2001 From: Richard R Date: Wed, 13 May 2026 23:44:13 -0600 Subject: [PATCH] Enable private caching headers for segment audio routes --- .../api/tts/segments/audio/fallback/route.ts | 3 ++- .../api/tts/segments/audio/presign/route.ts | 5 ++-- src/lib/server/tts/segments-audio.ts | 13 +++++++++++ tests/unit/tts-segments-audio-cache.spec.ts | 23 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/unit/tts-segments-audio-cache.spec.ts diff --git a/src/app/api/tts/segments/audio/fallback/route.ts b/src/app/api/tts/segments/audio/fallback/route.ts index b159305..9884217 100644 --- a/src/app/api/tts/segments/audio/fallback/route.ts +++ b/src/app/api/tts/segments/audio/fallback/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { isS3Configured } from '@/lib/server/storage/s3'; import { + buildSegmentAudioCacheHeaders, resolveCompletedSegmentAudio, streamAudioBuffer, ttsSegmentsS3NotConfiguredResponse, @@ -20,7 +21,7 @@ export async function GET(request: NextRequest) { return new NextResponse(streamAudioBuffer(audio), { headers: { 'Content-Type': 'audio/mpeg', - 'Cache-Control': 'no-store', + ...buildSegmentAudioCacheHeaders('fallback'), }, }); } catch (error) { diff --git a/src/app/api/tts/segments/audio/presign/route.ts b/src/app/api/tts/segments/audio/presign/route.ts index 8ea4706..746b204 100644 --- a/src/app/api/tts/segments/audio/presign/route.ts +++ b/src/app/api/tts/segments/audio/presign/route.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { isS3Configured } from '@/lib/server/storage/s3'; import { + buildSegmentAudioCacheHeaders, resolveCompletedSegmentAudio, ttsSegmentsS3NotConfiguredResponse, } from '@/lib/server/tts/segments-audio'; @@ -24,13 +25,13 @@ export async function GET(request: NextRequest) { }); return NextResponse.redirect(fallbackUrl, { status: 307, - headers: { 'Cache-Control': 'no-store' }, + headers: buildSegmentAudioCacheHeaders('redirect'), }); } return NextResponse.redirect(directUrl, { status: 307, - headers: { 'Cache-Control': 'no-store' }, + headers: buildSegmentAudioCacheHeaders('redirect'), }); } catch (error) { console.error('Error creating segment audio signature:', error); diff --git a/src/lib/server/tts/segments-audio.ts b/src/lib/server/tts/segments-audio.ts index a9d2a2a..c921407 100644 --- a/src/lib/server/tts/segments-audio.ts +++ b/src/lib/server/tts/segments-audio.ts @@ -11,6 +11,10 @@ export type ResolvedSegmentAudio = { audioKey: string; }; +export const TTS_SEGMENT_REDIRECT_CACHE_CONTROL = 'private, max-age=60, stale-while-revalidate=30'; +export const TTS_SEGMENT_FALLBACK_CACHE_CONTROL = 'private, max-age=300, stale-while-revalidate=60'; +export const TTS_SEGMENT_AUDIO_VARY = 'Cookie, Authorization'; + export function ttsSegmentsS3NotConfiguredResponse(): NextResponse { return NextResponse.json( { error: 'TTS segments storage is not configured. Set S3_* environment variables.' }, @@ -27,6 +31,15 @@ export function streamAudioBuffer(buffer: Buffer): ReadableStream { }); } +export function buildSegmentAudioCacheHeaders(kind: 'redirect' | 'fallback'): Record { + return { + 'Cache-Control': kind === 'redirect' + ? TTS_SEGMENT_REDIRECT_CACHE_CONTROL + : TTS_SEGMENT_FALLBACK_CACHE_CONTROL, + Vary: TTS_SEGMENT_AUDIO_VARY, + }; +} + export async function resolveCompletedSegmentAudio( request: NextRequest, ): Promise { diff --git a/tests/unit/tts-segments-audio-cache.spec.ts b/tests/unit/tts-segments-audio-cache.spec.ts new file mode 100644 index 0000000..8ef9404 --- /dev/null +++ b/tests/unit/tts-segments-audio-cache.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from '@playwright/test'; +import { + buildSegmentAudioCacheHeaders, + TTS_SEGMENT_AUDIO_VARY, + TTS_SEGMENT_FALLBACK_CACHE_CONTROL, + TTS_SEGMENT_REDIRECT_CACHE_CONTROL, +} from '../../src/lib/server/tts/segments-audio'; + +test.describe('tts segment audio cache headers', () => { + test('builds redirect cache headers', () => { + expect(buildSegmentAudioCacheHeaders('redirect')).toEqual({ + 'Cache-Control': TTS_SEGMENT_REDIRECT_CACHE_CONTROL, + Vary: TTS_SEGMENT_AUDIO_VARY, + }); + }); + + test('builds fallback cache headers', () => { + expect(buildSegmentAudioCacheHeaders('fallback')).toEqual({ + 'Cache-Control': TTS_SEGMENT_FALLBACK_CACHE_CONTROL, + Vary: TTS_SEGMENT_AUDIO_VARY, + }); + }); +});