fix(backups,files): auto-backups rejected by validator; trip file download broken after cookie migration

Fixes #773: isValidBackupFilename regex anchored to ^backup- rejected all
auto-backup-* filenames, causing 400 on download/restore/delete. Broadened
to ^(?:auto-)?backup-.

Fixes #774: three regressions in the trip Files tab —
- openFile import shadowed by a local function of the same name inside
  FileManager; PDF preview modal was calling the local with a URL string,
  corrupting state and crashing on the second click (mime_type read on
  undefined). Fixed by aliasing the import as openFileUrl.
- GET /:id/download used a bespoke authenticateDownload that checked only
  Bearer header and ?token= query param, ignoring the trek_session cookie.
  After the JWT-to-cookie migration the client sends cookies only, so every
  download silently 401-ed. Extended authenticateDownload to accept req and
  check cookie → Bearer → query token in priority order.
- files.download and files.openError translation keys were missing from all
  15 locale files; t() was returning the raw key as a truthy string,
  defeating the || 'Download' fallback.
This commit is contained in:
jubnl
2026-04-21 11:18:17 +02:00
parent ee31c78db8
commit 5eaf7492dc
21 changed files with 85 additions and 22 deletions
+4 -4
View File
@@ -10,7 +10,7 @@ import { useCanDo } from '../../store/permissionsStore'
import { useTripStore } from '../../store/tripStore'
import { getAuthUrl } from '../../api/authUrl'
import { downloadFile, openFile } from '../../utils/fileDownload'
import { downloadFile, openFile as openFileUrl } from '../../utils/fileDownload'
function isImage(mimeType) {
if (!mimeType) return false
@@ -113,7 +113,7 @@ function ImageLightbox({ files, initialIndex, onClose }: ImageLightboxProps) {
</span>
<div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
<button
onClick={() => openFile(file.url).catch(() => {})}
onClick={() => openFileUrl(file.url, file.original_name).catch(() => {})}
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'rgba(255,255,255,0.7)', display: 'flex', padding: 4 }}
title={t('files.openTab')}>
<ExternalLink size={16} />
@@ -743,7 +743,7 @@ export default function FileManager({ files = [], onUpload, onDelete, onUpdate,
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{previewFile.original_name}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
<button
onClick={() => openFile(previewFile.url).catch(() => {})}
onClick={() => openFileUrl(previewFile.url, previewFile.original_name).catch(() => toast.error(t('files.openError')))}
style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, color: 'var(--text-muted)', background: 'none', border: 'none', cursor: 'pointer', textDecoration: 'none', padding: '4px 8px', borderRadius: 6, transition: 'color 0.15s' }}
onMouseEnter={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-primary)'}
onMouseLeave={e => (e.currentTarget as HTMLElement).style.color = 'var(--text-muted)'}>
@@ -771,7 +771,7 @@ export default function FileManager({ files = [], onUpload, onDelete, onUpdate,
title={previewFile.original_name}
>
<p style={{ padding: 24, textAlign: 'center', color: 'var(--text-muted)' }}>
<button onClick={() => openFile(previewFile.url).catch(() => {})} style={{ color: 'var(--text-primary)', textDecoration: 'underline', background: 'none', border: 'none', cursor: 'pointer', font: 'inherit' }}>{t('files.downloadPdf')}</button>
<button onClick={() => openFileUrl(previewFile.url, previewFile.original_name).catch(() => toast.error(t('files.openError')))} style={{ color: 'var(--text-primary)', textDecoration: 'underline', background: 'none', border: 'none', cursor: 'pointer', font: 'inherit' }}>{t('files.downloadPdf')}</button>
</p>
</object>
</div>
+2
View File
@@ -1222,6 +1222,8 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'الملفات',
'files.pageTitle': 'الملفات والمستندات',
'files.subtitle': '{count} ملف لـ {trip}',
'files.download': 'تنزيل',
'files.openError': 'تعذر فتح الملف',
'files.downloadPdf': 'تنزيل PDF',
'files.count': '{count} ملفات',
'files.countSingular': 'ملف واحد',
+2
View File
@@ -1191,6 +1191,8 @@ const br: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'Arquivos',
'files.pageTitle': 'Arquivos e documentos',
'files.subtitle': '{count} arquivos para {trip}',
'files.download': 'Baixar',
'files.openError': 'Não foi possível abrir o arquivo',
'files.downloadPdf': 'Baixar PDF',
'files.count': '{count} arquivos',
'files.countSingular': '1 arquivo',
+2
View File
@@ -1220,6 +1220,8 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'Soubory',
'files.pageTitle': 'Soubory a dokumenty',
'files.subtitle': '{count} souborů pro {trip}',
'files.download': 'Stáhnout',
'files.openError': 'Soubor nelze otevřít',
'files.downloadPdf': 'Stáhnout PDF',
'files.count': '{count} souborů',
'files.countSingular': '1 soubor',
+2
View File
@@ -1224,6 +1224,8 @@ const de: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'Dateien',
'files.pageTitle': 'Dateien & Dokumente',
'files.subtitle': '{count} Dateien für {trip}',
'files.download': 'Herunterladen',
'files.openError': 'Datei konnte nicht geöffnet werden',
'files.downloadPdf': 'PDF herunterladen',
'files.count': '{count} Dateien',
'files.countSingular': '1 Datei',
+2
View File
@@ -1281,6 +1281,8 @@ const en: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'Files',
'files.pageTitle': 'Files & Documents',
'files.subtitle': '{count} files for {trip}',
'files.download': 'Download',
'files.openError': 'Could not open file',
'files.downloadPdf': 'Download PDF',
'files.count': '{count} files',
'files.countSingular': '1 file',
+2
View File
@@ -1168,6 +1168,8 @@ const es: Record<string, string> = {
'files.title': 'Archivos',
'files.pageTitle': 'Archivos y documentos',
'files.subtitle': '{count} archivos para {trip}',
'files.download': 'Descargar',
'files.openError': 'No se pudo abrir el archivo',
'files.downloadPdf': 'Descargar PDF',
'files.count': '{count} archivos',
'files.countSingular': '1 archivo',
+2
View File
@@ -1218,6 +1218,8 @@ const fr: Record<string, string> = {
'files.title': 'Fichiers',
'files.pageTitle': 'Fichiers et documents',
'files.subtitle': '{count} fichiers pour {trip}',
'files.download': 'Télécharger',
'files.openError': "Impossible d'ouvrir le fichier",
'files.downloadPdf': 'Télécharger le PDF',
'files.count': '{count} fichiers',
'files.countSingular': '1 fichier',
+2
View File
@@ -1219,6 +1219,8 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'Fájlok',
'files.pageTitle': 'Fájlok és dokumentumok',
'files.subtitle': '{count} fájl a következőhöz: {trip}',
'files.download': 'Letöltés',
'files.openError': 'A fájl megnyitása sikertelen',
'files.downloadPdf': 'PDF letöltése',
'files.count': '{count} fájl',
'files.countSingular': '1 fájl',
+2
View File
@@ -1279,6 +1279,8 @@ const id: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'File',
'files.pageTitle': 'File & Dokumen',
'files.subtitle': '{count} file untuk {trip}',
'files.download': 'Unduh',
'files.openError': 'Tidak dapat membuka file',
'files.downloadPdf': 'Unduh PDF',
'files.count': '{count} file',
'files.countSingular': '1 berkas',
+2
View File
@@ -1219,6 +1219,8 @@ const it: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'File',
'files.pageTitle': 'File e documenti',
'files.subtitle': '{count} file per {trip}',
'files.download': 'Scarica',
'files.openError': 'Impossibile aprire il file',
'files.downloadPdf': 'Scarica PDF',
'files.count': '{count} file',
'files.countSingular': '1 documento',
+2
View File
@@ -1218,6 +1218,8 @@ const nl: Record<string, string> = {
'files.title': 'Bestanden',
'files.pageTitle': 'Bestanden en documenten',
'files.subtitle': '{count} bestanden voor {trip}',
'files.download': 'Downloaden',
'files.openError': 'Bestand kon niet worden geopend',
'files.downloadPdf': 'PDF downloaden',
'files.count': '{count} bestanden',
'files.countSingular': '1 bestand',
+2
View File
@@ -1170,6 +1170,8 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
'files.title': 'Pliki',
'files.pageTitle': 'Pliki i dokumenty',
'files.subtitle': '{count} plików dla {trip}',
'files.download': 'Pobierz',
'files.openError': 'Nie można otworzyć pliku',
'files.downloadPdf': 'Pobierz PDF',
'files.count': '{count} plików',
'files.countSingular': '1 plik',
+2
View File
@@ -1218,6 +1218,8 @@ const ru: Record<string, string> = {
'files.title': 'Файлы',
'files.pageTitle': 'Файлы и документы',
'files.subtitle': '{count} файлов для {trip}',
'files.download': 'Скачать',
'files.openError': 'Не удалось открыть файл',
'files.downloadPdf': 'Скачать PDF',
'files.count': '{count} файлов',
'files.countSingular': '1 файл',
+2
View File
@@ -1218,6 +1218,8 @@ const zh: Record<string, string> = {
'files.title': '文件',
'files.pageTitle': '文件与文档',
'files.subtitle': '{trip} 的 {count} 个文件',
'files.download': '下载',
'files.openError': '无法打开文件',
'files.downloadPdf': '下载 PDF',
'files.count': '{count} 个文件',
'files.countSingular': '1 个文件',
+2
View File
@@ -1278,6 +1278,8 @@ const zhTw: Record<string, string> = {
'files.title': '檔案',
'files.pageTitle': '檔案與文件',
'files.subtitle': '{trip} 的 {count} 個檔案',
'files.download': '下載',
'files.openError': '無法開啟檔案',
'files.downloadPdf': '下載 PDF',
'files.count': '{count} 個檔案',
'files.countSingular': '1 個檔案',