openreader/src/lib/server/tts/segments-cache.ts
Richard R 59d0101c0c feat(server): implement unified structured logging with pino and enforce usage
Introduce `serverLogger` utility based on pino for consistent, structured logging across all server and API modules. Replace direct console logging with `serverLogger` and add request-scoped logging helpers. Update environment variable handling, documentation, and deployment guides to reflect new logging configuration (`LOG_FORMAT`, `LOG_LEVEL`, `COMPUTE_LOG_LEVEL`). Enforce no-console in server code via ESLint and add pino/pino-pretty dependencies.

This change standardizes log output, improves observability, and prepares the codebase for ingestion by log platforms.
2026-05-27 04:20:17 -06:00

83 lines
2.6 KiB
TypeScript

import { and, eq } from 'drizzle-orm';
import { db } from '@/db';
import { ttsSegmentEntries, ttsSegmentVariants } from '@/db/schema';
import { deleteTtsSegmentAudioObjects } from '@/lib/server/tts/segments-blobstore';
import type { ReaderType } from '@/types/user-state';
import { serverLogger } from '@/lib/server/logger';
type ClearTtsSegmentCacheInput = {
userId: string;
documentId: string;
documentVersion?: number;
readerType?: ReaderType;
};
export type ClearTtsSegmentCacheResult = {
deletedSegments: number;
requestedAudioObjects: number;
deletedAudioObjects: number;
warning?: string;
};
export async function clearTtsSegmentCache(
input: ClearTtsSegmentCacheInput,
): Promise<ClearTtsSegmentCacheResult> {
const conditions = [
eq(ttsSegmentEntries.userId, input.userId),
eq(ttsSegmentEntries.documentId, input.documentId),
];
if (typeof input.documentVersion === 'number' && Number.isFinite(input.documentVersion)) {
conditions.push(eq(ttsSegmentEntries.documentVersion, Math.floor(input.documentVersion)));
}
if (input.readerType) {
conditions.push(eq(ttsSegmentEntries.readerType, input.readerType));
}
const rows = (await db
.select({
segmentId: ttsSegmentVariants.segmentId,
audioKey: ttsSegmentVariants.audioKey,
})
.from(ttsSegmentVariants)
.innerJoin(
ttsSegmentEntries,
and(
eq(ttsSegmentEntries.segmentEntryId, ttsSegmentVariants.segmentEntryId),
eq(ttsSegmentEntries.userId, ttsSegmentVariants.userId),
),
)
.where(and(...conditions))) as Array<{ segmentId: string; audioKey: string | null }>;
await db.delete(ttsSegmentEntries).where(and(...conditions));
const audioKeys = rows
.map((row) => row.audioKey)
.filter((key): key is string => Boolean(key));
const uniqueAudioKeys = Array.from(new Set(audioKeys));
let deletedAudioObjects = 0;
let warning: string | undefined;
if (uniqueAudioKeys.length > 0) {
try {
deletedAudioObjects = await deleteTtsSegmentAudioObjects(uniqueAudioKeys);
if (deletedAudioObjects < uniqueAudioKeys.length) {
warning = `Deleted ${deletedAudioObjects} of ${uniqueAudioKeys.length} audio objects.`;
}
} catch (error) {
warning = error instanceof Error ? error.message : 'Failed deleting some audio objects';
serverLogger.warn({
documentId: input.documentId,
userId: input.userId,
error: warning,
}, 'Failed clearing some TTS segment audio objects:');
}
}
return {
deletedSegments: rows.length,
requestedAudioObjects: uniqueAudioKeys.length,
deletedAudioObjects,
...(warning ? { warning } : {}),
};
}