Фикс предпросмотра файлов с абсолютными self-URL

- extractFileKey понимает абсолютные URL (по pathname) — превью работает для уже записанных файлов
- normalizeFileUrl: абсолютные self-URL нормализуются в относительные при записи file-полей (REST field-values, MCP update_task_fields/upload-инструменты, bot-api, create task)
This commit is contained in:
2026-07-24 11:10:55 +03:00
parent b981614095
commit 59079992dc
6 changed files with 81 additions and 16 deletions

View File

@@ -175,6 +175,37 @@ export function extractFileUrls(fileValue: unknown): string[] {
return [];
}
// Нормализация self-URL файла к относительному виду.
// Абсолютный http(s) URL, pathname которого начинается с /api/files/ или /uploads/
// (т.е. указывает на этот же сервер), сводится к pathname — в БД храним относительные URL.
// Внешние URL и невалидные строки возвращаются без изменений.
export function normalizeFileUrl(url: string): string {
if (!url.startsWith('http://') && !url.startsWith('https://')) return url;
try {
const pathname = new URL(url).pathname;
if (pathname.startsWith('/api/files/') || pathname.startsWith('/uploads/')) {
return pathname;
}
} catch {
// невалидный URL — оставляем как есть
}
return url;
}
// Нормализует url внутри значения file-поля: одиночный объект {url,name,size}
// или массив таких объектов. Прочие значения возвращаются без изменений.
export function normalizeFileFieldUrls(value: unknown): unknown {
const normalizeItem = (item: unknown): unknown => {
if (item && typeof item === 'object' && typeof (item as Record<string, unknown>).url === 'string') {
const obj = item as Record<string, unknown>;
return { ...obj, url: normalizeFileUrl(obj.url as string) };
}
return item;
};
if (Array.isArray(value)) return value.map(normalizeItem);
return normalizeItem(value);
}
export async function deleteFileByUrl(url: string): Promise<void> {
if (isS3Enabled) {
// S3-режим: извлекаем ключ из URL вида /api/files/:key