diff --git a/backend/src/routes/files.ts b/backend/src/routes/files.ts
index 01e6e17..cc6463c 100644
--- a/backend/src/routes/files.ts
+++ b/backend/src/routes/files.ts
@@ -1,4 +1,4 @@
-import { Router } from 'express';
+import { Router, type Response } from 'express';
import path from 'path';
import fs from 'fs';
import { prisma } from '../lib/prisma';
@@ -8,6 +8,16 @@ import { AppError } from '../middleware/errorHandler';
const router = Router();
+/** Vercel 상세 창에서 PDF 등 iframe 미리보기 허용 */
+function allowCrossOriginPreview(res: Response) {
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
+ res.setHeader(
+ 'Content-Security-Policy',
+ "frame-ancestors 'self' https://eene-dashboard.vercel.app https://*.vercel.app http://localhost:3000",
+ );
+ res.removeHeader('X-Frame-Options');
+}
+
/** multer가 latin1로 전달하는 한글 파일명 복원 */
function fixOriginalName(name: string): string {
try {
@@ -70,6 +80,7 @@ router.get('/:id/view', async (req, res, next) => {
if (!file) throw new AppError(404, '파일을 찾을 수 없습니다.');
if (!fs.existsSync(file.path)) throw new AppError(404, '파일이 서버에 없습니다.');
+ allowCrossOriginPreview(res);
res.setHeader('Content-Type', file.mimetype);
res.setHeader('Content-Disposition', `inline; filename="${encodeURIComponent(file.originalName)}"`);
fs.createReadStream(file.path).pipe(res);
diff --git a/frontend/src/components/detail/ExcelPreview.tsx b/frontend/src/components/detail/ExcelPreview.tsx
index 7841175..32b5fa1 100644
--- a/frontend/src/components/detail/ExcelPreview.tsx
+++ b/frontend/src/components/detail/ExcelPreview.tsx
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import * as XLSX from 'xlsx';
+import { fileDownloadUrl, fileViewUrl } from '../../lib/apiClient';
interface ExcelPreviewProps {
fileId: string;
@@ -21,7 +22,7 @@ export function ExcelPreview({ fileId, fileName }: ExcelPreviewProps) {
(async () => {
try {
- const res = await fetch(`/api/files/${fileId}/view`);
+ const res = await fetch(fileViewUrl(fileId));
if (!res.ok) throw new Error('파일을 불러올 수 없습니다.');
const buffer = await res.arrayBuffer();
const wb = XLSX.read(buffer, { type: 'array' });
@@ -48,7 +49,7 @@ export function ExcelPreview({ fileId, fileName }: ExcelPreviewProps) {
{error}
{fileName} 다운로드
diff --git a/frontend/src/components/detail/ResultPreview.tsx b/frontend/src/components/detail/ResultPreview.tsx
index b3385c4..50dfd7a 100644
--- a/frontend/src/components/detail/ResultPreview.tsx
+++ b/frontend/src/components/detail/ResultPreview.tsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { ExcelPreview } from './ExcelPreview';
+import { fileViewUrl } from '../../lib/apiClient';
import { openLinkOnRightMonitor } from '../../lib/dualMonitor';
import { fileDisplayName, isExcelFile, isVideoFile } from '../../lib/fileDisplay';
import type { FileRecord, MilestoneLink } from '../../types';
@@ -53,7 +54,7 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP
if (isExcel) {
return ;
}
- const src = `/api/files/${activeFile.id}/view`;
+ const src = fileViewUrl(activeFile.id);
if (isImage) {
return (
a.sortOrder - b.sortOrder);
const uploads: PendingFileUpload[] = [];
const existingEdits: ExistingFileEdit[] = [];
@@ -312,7 +321,7 @@ export function StageModal({
}
});
- await onSave(form, {
+ await onSave(saveForm, {
uploads,
existingEdits,
deletedFileIds,
diff --git a/frontend/src/lib/apiClient.ts b/frontend/src/lib/apiClient.ts
index 572c8f8..00f4c94 100644
--- a/frontend/src/lib/apiClient.ts
+++ b/frontend/src/lib/apiClient.ts
@@ -16,6 +16,14 @@ export const apiClient = axios.create({
},
});
+export function fileViewUrl(fileId: string): string {
+ return `${baseURL}/files/${fileId}/view`;
+}
+
+export function fileDownloadUrl(fileId: string): string {
+ return `${baseURL}/files/${fileId}/download`;
+}
+
apiClient.interceptors.request.use((config) => {
if (config.data instanceof FormData) {
delete config.headers['Content-Type'];
diff --git a/frontend/src/pages/DetailPage.tsx b/frontend/src/pages/DetailPage.tsx
index c8c6cce..90bbd5b 100644
--- a/frontend/src/pages/DetailPage.tsx
+++ b/frontend/src/pages/DetailPage.tsx
@@ -286,7 +286,7 @@ function DetailView({ task }: { task: TaskWithRelations }) {
startDate: data.startDate || undefined,
dueDate: data.dueDate || undefined,
progress: data.progress,
- links: data.links.length > 0 ? JSON.stringify(data.links) : undefined,
+ links: data.links,
};
let milestoneId: string;