Compare commits
No commits in common. "0742b101b39ec43cfa28cf3ac3490af37659447a" and "4c9ddc0371661cfc9b36060a11adccba46ae9f08" have entirely different histories.
0742b101b3
...
4c9ddc0371
@ -1,2 +1,2 @@
|
||||
import{a as t}from"./index-Dl7ujaUD.js";import"./radix-vendor-DA0cB_hD.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-BZmDhLpD.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-YTj2hkRM.js";async function m(n){return(await t.post(`/conclusions/${n}/generate`)).data.data}async function d(n,o){return(await t.post(`/conclusions/${n}/finalize`,{finalRemark:o})).data.data}async function f(n){return(await t.get(`/conclusions/${n}`)).data.data}export{d as finalizeConclusion,m as generateConclusion,f as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-DMcCouEt.js.map
|
||||
import{a as t}from"./index-ZrAGeZ0h.js";import"./radix-vendor-DA0cB_hD.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-BZmDhLpD.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-YTj2hkRM.js";async function m(n){return(await t.post(`/conclusions/${n}/generate`)).data.data}async function d(n,o){return(await t.post(`/conclusions/${n}/finalize`,{finalRemark:o})).data.data}async function f(n){return(await t.get(`/conclusions/${n}`)).data.data}export{d as finalizeConclusion,m as generateConclusion,f as getConclusion};
|
||||
//# sourceMappingURL=conclusionApi-D1OyuSCp.js.map
|
||||
@ -1 +1 @@
|
||||
{"version":3,"file":"conclusionApi-DMcCouEt.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAKA,eAAsBC,EAAcJ,EAA8C,CAEhF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB"}
|
||||
{"version":3,"file":"conclusionApi-D1OyuSCp.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAKA,eAAsBC,EAAcJ,EAA8C,CAEhF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
build/assets/index-ZrAGeZ0h.js.map
Normal file
1
build/assets/index-ZrAGeZ0h.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,2 +1,2 @@
|
||||
import{g as s}from"./index-Dl7ujaUD.js";import"./radix-vendor-DA0cB_hD.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-BZmDhLpD.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-YTj2hkRM.js";function R(o){const{requestId:e,status:t,request:a,navigate:r}=o;if((t==null?void 0:t.toLowerCase())==="draft"||t==="DRAFT"){r(`/edit-request/${e}`);return}const i=s(e);r(i)}export{R as navigateToRequest};
|
||||
//# sourceMappingURL=requestNavigation-BOiRTAb7.js.map
|
||||
import{g as s}from"./index-ZrAGeZ0h.js";import"./radix-vendor-DA0cB_hD.js";import"./charts-vendor-Cji9-Yri.js";import"./utils-vendor-DHm03ykU.js";import"./ui-vendor-BZmDhLpD.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-YTj2hkRM.js";function R(o){const{requestId:e,status:t,request:a,navigate:r}=o;if((t==null?void 0:t.toLowerCase())==="draft"||t==="DRAFT"){r(`/edit-request/${e}`);return}const i=s(e);r(i)}export{R as navigateToRequest};
|
||||
//# sourceMappingURL=requestNavigation-3p2DCHnU.js.map
|
||||
@ -1 +1 @@
|
||||
{"version":3,"file":"requestNavigation-BOiRTAb7.js","sources":["../../src/utils/requestNavigation.ts"],"sourcesContent":["/**\r\n * Global Request Navigation Utility\r\n * \r\n * Centralized navigation logic for request-related routes.\r\n * This utility decides where to navigate when clicking on request cards\r\n * from anywhere in the application.\r\n * \r\n * Features:\r\n * - Single point of navigation logic\r\n * - Handles draft vs active requests\r\n * - Supports different flow types (CUSTOM, DEALER_CLAIM)\r\n * - Type-safe navigation\r\n */\r\n\r\nimport { NavigateFunction } from 'react-router-dom';\r\nimport { getRequestDetailRoute, RequestFlowType } from './requestTypeUtils';\r\n\r\nexport interface RequestNavigationOptions {\r\n requestId: string;\r\n requestTitle?: string;\r\n status?: string;\r\n request?: any; // Full request object if available\r\n navigate: NavigateFunction;\r\n}\r\n\r\n/**\r\n * Navigate to the appropriate request detail page based on request type\r\n * \r\n * This is the single point of navigation for all request cards.\r\n * It handles:\r\n * - Draft requests (navigate to edit)\r\n * - Different flow types (CUSTOM, DEALER_CLAIM)\r\n * - Status-based routing\r\n */\r\nexport function navigateToRequest(options: RequestNavigationOptions): void {\r\n const { requestId, status, request, navigate } = options;\r\n\r\n // Check if request is a draft - if so, route to edit form instead of detail view\r\n const isDraft = status?.toLowerCase() === 'draft' || status === 'DRAFT';\r\n if (isDraft) {\r\n navigate(`/edit-request/${requestId}`);\r\n return;\r\n }\r\n\r\n // Determine the appropriate route based on request type\r\n const route = getRequestDetailRoute(requestId, request);\r\n navigate(route);\r\n}\r\n\r\n/**\r\n * Navigate to create a new request based on flow type\r\n */\r\nexport function navigateToCreateRequest(\r\n navigate: NavigateFunction,\r\n flowType: RequestFlowType = 'CUSTOM'\r\n): void {\r\n const route = flowType === 'DEALER_CLAIM' \r\n ? '/claim-management' \r\n : '/new-request';\r\n navigate(route);\r\n}\r\n\r\n/**\r\n * Create a navigation handler function for request cards\r\n * This can be used directly in onClick handlers\r\n */\r\nexport function createRequestNavigationHandler(\r\n navigate: NavigateFunction\r\n) {\r\n return (requestId: string, requestTitle?: string, status?: string, request?: any) => {\r\n navigateToRequest({\r\n requestId,\r\n requestTitle,\r\n status,\r\n request,\r\n navigate,\r\n });\r\n };\r\n}\r\n"],"names":["navigateToRequest","options","requestId","status","request","navigate","route","getRequestDetailRoute"],"mappings":"6RAkCO,SAASA,EAAkBC,EAAyC,CACzE,KAAM,CAAE,UAAAC,EAAW,OAAAC,EAAQ,QAAAC,EAAS,SAAAC,GAAaJ,EAIjD,IADgBE,GAAA,YAAAA,EAAQ,iBAAkB,SAAWA,IAAW,QACnD,CACXE,EAAS,iBAAiBH,CAAS,EAAE,EACrC,MACF,CAGA,MAAMI,EAAQC,EAAsBL,CAAkB,EACtDG,EAASC,CAAK,CAChB"}
|
||||
{"version":3,"file":"requestNavigation-3p2DCHnU.js","sources":["../../src/utils/requestNavigation.ts"],"sourcesContent":["/**\r\n * Global Request Navigation Utility\r\n * \r\n * Centralized navigation logic for request-related routes.\r\n * This utility decides where to navigate when clicking on request cards\r\n * from anywhere in the application.\r\n * \r\n * Features:\r\n * - Single point of navigation logic\r\n * - Handles draft vs active requests\r\n * - Supports different flow types (CUSTOM, DEALER_CLAIM)\r\n * - Type-safe navigation\r\n */\r\n\r\nimport { NavigateFunction } from 'react-router-dom';\r\nimport { getRequestDetailRoute, RequestFlowType } from './requestTypeUtils';\r\n\r\nexport interface RequestNavigationOptions {\r\n requestId: string;\r\n requestTitle?: string;\r\n status?: string;\r\n request?: any; // Full request object if available\r\n navigate: NavigateFunction;\r\n}\r\n\r\n/**\r\n * Navigate to the appropriate request detail page based on request type\r\n * \r\n * This is the single point of navigation for all request cards.\r\n * It handles:\r\n * - Draft requests (navigate to edit)\r\n * - Different flow types (CUSTOM, DEALER_CLAIM)\r\n * - Status-based routing\r\n */\r\nexport function navigateToRequest(options: RequestNavigationOptions): void {\r\n const { requestId, status, request, navigate } = options;\r\n\r\n // Check if request is a draft - if so, route to edit form instead of detail view\r\n const isDraft = status?.toLowerCase() === 'draft' || status === 'DRAFT';\r\n if (isDraft) {\r\n navigate(`/edit-request/${requestId}`);\r\n return;\r\n }\r\n\r\n // Determine the appropriate route based on request type\r\n const route = getRequestDetailRoute(requestId, request);\r\n navigate(route);\r\n}\r\n\r\n/**\r\n * Navigate to create a new request based on flow type\r\n */\r\nexport function navigateToCreateRequest(\r\n navigate: NavigateFunction,\r\n flowType: RequestFlowType = 'CUSTOM'\r\n): void {\r\n const route = flowType === 'DEALER_CLAIM' \r\n ? '/claim-management' \r\n : '/new-request';\r\n navigate(route);\r\n}\r\n\r\n/**\r\n * Create a navigation handler function for request cards\r\n * This can be used directly in onClick handlers\r\n */\r\nexport function createRequestNavigationHandler(\r\n navigate: NavigateFunction\r\n) {\r\n return (requestId: string, requestTitle?: string, status?: string, request?: any) => {\r\n navigateToRequest({\r\n requestId,\r\n requestTitle,\r\n status,\r\n request,\r\n navigate,\r\n });\r\n };\r\n}\r\n"],"names":["navigateToRequest","options","requestId","status","request","navigate","route","getRequestDetailRoute"],"mappings":"6RAkCO,SAASA,EAAkBC,EAAyC,CACzE,KAAM,CAAE,UAAAC,EAAW,OAAAC,EAAQ,QAAAC,EAAS,SAAAC,GAAaJ,EAIjD,IADgBE,GAAA,YAAAA,EAAQ,iBAAkB,SAAWA,IAAW,QACnD,CACXE,EAAS,iBAAiBH,CAAS,EAAE,EACrC,MACF,CAGA,MAAMI,EAAQC,EAAsBL,CAAkB,EACtDG,EAASC,CAAK,CAChB"}
|
||||
@ -52,7 +52,7 @@
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-Dl7ujaUD.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-ZrAGeZ0h.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-Cji9-Yri.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-DA0cB_hD.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DHm03ykU.js">
|
||||
|
||||
@ -84,7 +84,6 @@ export class AuthController {
|
||||
displayName: user.displayName,
|
||||
department: user.department,
|
||||
designation: user.designation,
|
||||
jobTitle: user.jobTitle,
|
||||
phone: user.phone,
|
||||
location: user.location,
|
||||
role: user.role,
|
||||
|
||||
@ -335,40 +335,8 @@ router.get('/documents/:documentId/preview',
|
||||
|
||||
// Local file handling - check if storageUrl is a local path (starts with /uploads/)
|
||||
if (storageUrl && storageUrl.startsWith('/uploads/')) {
|
||||
// Extract relative path from storageUrl (remove /uploads/ prefix)
|
||||
const relativePath = storageUrl.replace(/^\/uploads\//, '');
|
||||
const absolutePath = path.join(UPLOAD_DIR, relativePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
res.status(404).json({ success: false, error: 'File not found on server' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Set CORS headers to allow blob URL creation when served from same origin
|
||||
const origin = req.headers.origin;
|
||||
if (origin) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
res.setHeader('Access-Control-Expose-Headers', 'Content-Type, Content-Disposition');
|
||||
|
||||
// Set appropriate content type
|
||||
res.contentType(fileType || 'application/octet-stream');
|
||||
|
||||
// For images and PDFs, allow inline viewing
|
||||
const isPreviewable = fileType && (fileType.includes('image') || fileType.includes('pdf'));
|
||||
if (isPreviewable) {
|
||||
res.setHeader('Content-Disposition', `inline; filename="${fileName}"`);
|
||||
} else {
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
|
||||
}
|
||||
|
||||
res.sendFile(absolutePath, (err) => {
|
||||
if (err && !res.headersSent) {
|
||||
res.status(500).json({ success: false, error: 'Failed to serve file' });
|
||||
}
|
||||
});
|
||||
// File is served by express.static middleware, redirect to the storage URL
|
||||
res.redirect(storageUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -521,39 +489,15 @@ router.get('/documents/:documentId/download',
|
||||
|
||||
// Local file handling - check if storageUrl is a local path (starts with /uploads/)
|
||||
if (storageUrl && storageUrl.startsWith('/uploads/')) {
|
||||
// Extract relative path from storageUrl (remove /uploads/ prefix)
|
||||
const relativePath = storageUrl.replace(/^\/uploads\//, '');
|
||||
const absolutePath = path.join(UPLOAD_DIR, relativePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
res.status(404).json({ success: false, error: 'File not found on server' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Set CORS headers
|
||||
const origin = req.headers.origin;
|
||||
if (origin) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
res.setHeader('Access-Control-Expose-Headers', 'Content-Type, Content-Disposition');
|
||||
|
||||
// Set headers for download
|
||||
const fileTypeForDownload = (document as any).mimeType || (document as any).mime_type || 'application/octet-stream';
|
||||
res.setHeader('Content-Type', fileTypeForDownload);
|
||||
res.setHeader('Content-Disposition', createContentDisposition('attachment', fileName));
|
||||
|
||||
res.download(absolutePath, fileName, (err) => {
|
||||
if (err && !res.headersSent) {
|
||||
res.status(500).json({ success: false, error: 'Failed to download file' });
|
||||
}
|
||||
});
|
||||
// File is served by express.static middleware, redirect to the storage URL
|
||||
res.redirect(storageUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy local file handling (absolute path stored in filePath)
|
||||
// Resolve relative path if needed
|
||||
const path = require('path');
|
||||
const { UPLOAD_DIR } = require('../config/storage');
|
||||
const absolutePath = filePath && !path.isAbsolute(filePath)
|
||||
? path.join(UPLOAD_DIR, filePath)
|
||||
: filePath;
|
||||
@ -588,45 +532,15 @@ router.get('/work-notes/attachments/:attachmentId/preview',
|
||||
|
||||
// Local file handling - check if storageUrl is a local path (starts with /uploads/)
|
||||
if (fileInfo.storageUrl && fileInfo.storageUrl.startsWith('/uploads/')) {
|
||||
// Extract relative path from storageUrl (remove /uploads/ prefix)
|
||||
const relativePath = fileInfo.storageUrl.replace(/^\/uploads\//, '');
|
||||
const absolutePath = path.join(UPLOAD_DIR, relativePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
res.status(404).json({ success: false, error: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Set CORS headers to allow blob URL creation when served from same origin
|
||||
const origin = req.headers.origin;
|
||||
if (origin) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
res.setHeader('Access-Control-Expose-Headers', 'Content-Type, Content-Disposition');
|
||||
|
||||
// Set appropriate content type
|
||||
res.contentType(fileInfo.fileType || 'application/octet-stream');
|
||||
|
||||
// For images and PDFs, allow inline viewing
|
||||
const isPreviewable = fileInfo.fileType && (fileInfo.fileType.includes('image') || fileInfo.fileType.includes('pdf'));
|
||||
if (isPreviewable) {
|
||||
res.setHeader('Content-Disposition', `inline; filename="${fileInfo.fileName}"`);
|
||||
} else {
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${fileInfo.fileName}"`);
|
||||
}
|
||||
|
||||
res.sendFile(absolutePath, (err) => {
|
||||
if (err && !res.headersSent) {
|
||||
res.status(500).json({ success: false, error: 'Failed to serve file' });
|
||||
}
|
||||
});
|
||||
// File is served by express.static middleware, redirect to the storage URL
|
||||
res.redirect(fileInfo.storageUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy local file handling (absolute path stored in filePath)
|
||||
// Resolve relative path if needed
|
||||
const path = require('path');
|
||||
const { UPLOAD_DIR } = require('../config/storage');
|
||||
const absolutePath = fileInfo.filePath && !path.isAbsolute(fileInfo.filePath)
|
||||
? path.join(UPLOAD_DIR, fileInfo.filePath)
|
||||
: fileInfo.filePath;
|
||||
@ -680,34 +594,15 @@ router.get('/work-notes/attachments/:attachmentId/download',
|
||||
|
||||
// Local file handling - check if storageUrl is a local path (starts with /uploads/)
|
||||
if (fileInfo.storageUrl && fileInfo.storageUrl.startsWith('/uploads/')) {
|
||||
// Extract relative path from storageUrl (remove /uploads/ prefix)
|
||||
const relativePath = fileInfo.storageUrl.replace(/^\/uploads\//, '');
|
||||
const absolutePath = path.join(UPLOAD_DIR, relativePath);
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
res.status(404).json({ success: false, error: 'File not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Set CORS headers
|
||||
const origin = req.headers.origin;
|
||||
if (origin) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
}
|
||||
res.setHeader('Access-Control-Expose-Headers', 'Content-Type, Content-Disposition');
|
||||
|
||||
res.download(absolutePath, fileInfo.fileName, (err) => {
|
||||
if (err && !res.headersSent) {
|
||||
res.status(500).json({ success: false, error: 'Failed to download file' });
|
||||
}
|
||||
});
|
||||
// File is served by express.static middleware, redirect to the storage URL
|
||||
res.redirect(fileInfo.storageUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy local file handling (absolute path stored in filePath)
|
||||
// Resolve relative path if needed
|
||||
const path = require('path');
|
||||
const { UPLOAD_DIR } = require('../config/storage');
|
||||
const absolutePath = fileInfo.filePath && !path.isAbsolute(fileInfo.filePath)
|
||||
? path.join(UPLOAD_DIR, fileInfo.filePath)
|
||||
: fileInfo.filePath;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user