fix: ENOENT download error for archive-only converters (PDFMathTranslate/MinerU)

- Add existsSync checks in download.tsx before file access
- Fix normalizeFiletype.ts: md-t/md-i return original format (not tar.gz)
- Add outputMode: 'archive' support in main.ts with auto .tar suffix
- PDFMathTranslate outputs translated-<lang>.pdf + bilingual-<lang>.pdf (no original)
- MinerU outputs complete folder in .tar with logging
- Hide preview icon for .tar files in results.tsx
- Update Bun version: 1.2.2 -> 1.3.6
- Add Step 19: auto-deploy to remote server after Docker push

All 159 tests pass.
This commit is contained in:
Your Name 2026-01-22 00:47:37 +08:00
parent 1f800c3619
commit 912546aaa7
10 changed files with 190 additions and 63 deletions

View file

@ -1,3 +1,4 @@
import { existsSync } from "node:fs";
import { Elysia } from "elysia";
import sanitize from "sanitize-filename";
import { outputDir } from "..";
@ -10,7 +11,7 @@ export const download = new Elysia()
.use(userService)
.get(
"/download/:userId/:jobId/:fileName",
async ({ params, redirect, user }) => {
async ({ params, redirect, set, user }) => {
const userId = user.id;
const job = await db
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
@ -24,6 +25,14 @@ export const download = new Elysia()
const fileName = sanitize(decodeURIComponent(params.fileName));
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
// 檢查檔案是否存在
if (!existsSync(filePath)) {
console.error(`[Download] File not found: ${filePath}`);
set.status = 404;
return { error: "File not found", path: filePath };
}
return Bun.file(filePath);
},
{
@ -32,7 +41,7 @@ export const download = new Elysia()
)
.get(
"/archive/:jobId",
async ({ params, redirect, user }) => {
async ({ params, redirect, set, user }) => {
const userId = user.id;
const job = await db
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
@ -45,9 +54,23 @@ export const download = new Elysia()
const jobId = decodeURIComponent(params.jobId);
const outputPath = `${outputDir}${userId}/${jobId}`;
// 檢查輸出目錄是否存在
if (!existsSync(outputPath)) {
console.error(`[Archive] Output directory not found: ${outputPath}`);
set.status = 404;
return { error: "Output directory not found", path: outputPath };
}
// 使用統一的封裝管理器建立 .tar不壓縮
const outputTar = await createJobArchive(outputPath, jobId);
// 檢查 archive 是否成功建立
if (!existsSync(outputTar)) {
console.error(`[Archive] Failed to create archive: ${outputTar}`);
set.status = 500;
return { error: "Failed to create archive", path: outputTar };
}
return Bun.file(outputTar);
},
{

View file

@ -97,35 +97,41 @@ function ResultsArticle({
</tr>
</thead>
<tbody>
{files.map((file) => (
<tr>
<td safe class="max-w-[20vw] truncate">
{file.output_file_name}
</td>
<td safe>{file.status}</td>
<td class="flex flex-row gap-4">
<a
class={`
text-accent-500 underline
hover:text-accent-400
`}
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
>
<EyeIcon />
</a>
<a
class={`
text-accent-500 underline
hover:text-accent-400
`}
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
download={file.output_file_name}
>
<DownloadIcon />
</a>
</td>
</tr>
))}
{files.map((file) => {
const isTarFile = file.output_file_name.endsWith(".tar");
return (
<tr>
<td safe class="max-w-[20vw] truncate">
{file.output_file_name}
</td>
<td safe>{file.status}</td>
<td class="flex flex-row gap-4">
{/* Hide preview icon for .tar files */}
{!isTarFile && (
<a
class={`
text-accent-500 underline
hover:text-accent-400
`}
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
>
<EyeIcon />
</a>
)}
<a
class={`
text-accent-500 underline
hover:text-accent-400
`}
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
download={file.output_file_name}
>
<DownloadIcon />
</a>
</td>
</tr>
);
})}
</tbody>
</table>
</article>