fix: persist uploads on Render disk and show missing file notice

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-06-05 23:01:00 +09:00
parent 4960fe7352
commit c93df1ae40
3 changed files with 62 additions and 3 deletions

View File

@@ -48,6 +48,9 @@ export function ExcelPreview({ fileId, fileName }: ExcelPreviewProps) {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-3 p-6 text-center">
<p className="text-lg text-white/60">{error}</p>
<p className="text-sm text-white/40">
. .
</p>
<a
href={fileDownloadUrl(fileId)}
className="rounded bg-white/10 px-4 py-2 text-sm font-bold text-white hover:bg-white/20"

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { ExcelPreview } from './ExcelPreview';
import { fileViewUrl } from '../../lib/apiClient';
import { fileDownloadUrl, fileViewUrl } from '../../lib/apiClient';
import { openLinkOnRightMonitor } from '../../lib/dualMonitor';
import { fileDisplayName, isExcelFile, isVideoFile } from '../../lib/fileDisplay';
import type { FileRecord, MilestoneLink } from '../../types';
@@ -12,10 +12,32 @@ interface ResultPreviewProps {
hasSelectedStage: boolean;
}
function FileMissingNotice({ label, fileId }: { label: string; fileId: string }) {
return (
<div className="flex max-w-lg flex-col items-center gap-3 px-6 text-center">
<p className="text-xl font-bold text-white/70">{label}</p>
<p className="text-base leading-relaxed text-white/45">
.
<br />
· .
<br />
.
</p>
<a
href={fileDownloadUrl(fileId)}
className="rounded-lg bg-white/10 px-4 py-2 text-sm font-bold text-white/80 hover:bg-white/20"
>
</a>
</div>
);
}
export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewProps) {
const [fileId, setFileId] = useState<string | null>(null);
const [zoom, setZoom] = useState(1);
const [fullscreen, setFullscreen] = useState(false);
const [fileMissing, setFileMissing] = useState(false);
useEffect(() => {
if (files.length > 0) {
@@ -27,6 +49,25 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP
}, [files]);
const activeFile = fileId ? files.find((f) => f.id === fileId) ?? null : null;
useEffect(() => {
if (!activeFile || isExcelFile(activeFile)) {
setFileMissing(false);
return;
}
let cancelled = false;
setFileMissing(false);
fetch(fileViewUrl(activeFile.id), { method: 'HEAD' })
.then((res) => {
if (!cancelled) setFileMissing(!res.ok);
})
.catch(() => {
if (!cancelled) setFileMissing(true);
});
return () => {
cancelled = true;
};
}, [activeFile?.id, activeFile?.mimetype]);
const fileIndex = activeFile ? files.findIndex((f) => f.id === activeFile.id) : -1;
const isImage = activeFile?.mimetype.includes('image') ?? false;
const isVideo = activeFile ? isVideoFile(activeFile) : false;
@@ -54,6 +95,9 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP
if (isExcel) {
return <ExcelPreview fileId={activeFile.id} fileName={label} />;
}
if (fileMissing) {
return <FileMissingNotice label={label} fileId={activeFile.id} />;
}
const src = fileViewUrl(activeFile.id);
if (isImage) {
return (
@@ -63,12 +107,19 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP
className="max-h-full max-w-full object-contain transition-transform duration-150"
style={{ transform: `scale(${zoom})` }}
draggable={false}
onError={() => setFileMissing(true)}
/>
);
}
if (isVideo) {
return (
<video src={src} controls className="max-h-full max-w-full" title={label} />
<video
src={src}
controls
className="max-h-full max-w-full"
title={label}
onError={() => setFileMissing(true)}
/>
);
}
return (

View File

@@ -5,6 +5,11 @@ services:
rootDir: backend
buildCommand: npm install --include=dev && (npx prisma migrate deploy || true) && npx prisma db push && npx prisma generate && npm run build
startCommand: npx prisma db push && npm start
# 첨부 파일이 재배포 후에도 유지되도록 영구 디스크 (Render 유료 플랜 필요)
disk:
name: uploads-data
mountPath: /opt/render/project/src/backend/uploads
sizeGB: 1
envVars:
- key: DATABASE_URL
sync: false # Render 대시보드에서 직접 입력
@@ -17,7 +22,7 @@ services:
- key: JWT_EXPIRES_IN
value: 7d
- key: UPLOAD_DIR
value: ./uploads
value: /opt/render/project/src/backend/uploads
- key: MAX_FILE_SIZE_MB
value: 20
- key: NODE_ENV