Spaces:
Sleeping
Sleeping
| import { Router, Response } from 'express'; | |
| import { authenticateUser, AuthenticatedRequest } from '../middleware/auth'; | |
| import { getJobById } from '../../db/jobs'; | |
| import { createSignedUrl } from '../../db/storage'; | |
| import { config } from '../../config'; | |
| import { logger } from '../../utils/logger'; | |
| const router = Router(); | |
| function buildReportDownloadName(inputFileName: string): string { | |
| const base = String(inputFileName || 'report') | |
| .replace(/\.[^.]+$/, '') | |
| .replace(/[\\/:*?"<>|]+/g, ' ') | |
| .replace(/\s+/g, ' ') | |
| .trim() | |
| .slice(0, 140) || 'report'; | |
| return `RelVDev_${base}.pdf`; | |
| } | |
| function buildReceiptDownloadName(inputFileName: string): string { | |
| const base = String(inputFileName || 'receipt') | |
| .replace(/\.[^.]+$/, '') | |
| .replace(/[\\/:*?"<>|]+/g, ' ') | |
| .replace(/\s+/g, ' ') | |
| .trim() | |
| .slice(0, 132) || 'receipt'; | |
| return `RelVDev_Receipt_${base}.pdf`; | |
| } | |
| /** | |
| * GET /api/jobs/:jobId/report-url | |
| * | |
| * Creates a short-lived signed URL for a completed report PDF. The browser | |
| * should not sign private storage objects directly because storage RLS can hide | |
| * private files as "not found"; this endpoint verifies job ownership first and | |
| * then signs with the backend service role. | |
| */ | |
| router.get( | |
| '/api/jobs/:jobId/report-url', | |
| authenticateUser, | |
| async (req, res: Response): Promise<void> => { | |
| const authReq = req as AuthenticatedRequest; | |
| const jobId = String(authReq.params.jobId || ''); | |
| try { | |
| const job = await getJobById(jobId); | |
| if (!job) { | |
| res.status(404).json({ error: 'Job not found' }); | |
| return; | |
| } | |
| if (job.user_id !== authReq.userId) { | |
| res.status(403).json({ error: 'Forbidden' }); | |
| return; | |
| } | |
| if (!job.output_pdf_path) { | |
| res.status(404).json({ error: 'Report PDF is not available yet' }); | |
| return; | |
| } | |
| if ( | |
| job.output_pdf_expires_at && | |
| new Date(job.output_pdf_expires_at).getTime() < Date.now() | |
| ) { | |
| res.status(410).json({ error: 'Report PDF has expired' }); | |
| return; | |
| } | |
| const signedUrl = await createSignedUrl( | |
| config.reportBucket, | |
| job.output_pdf_path, | |
| 300, | |
| buildReportDownloadName(job.input_file_name), | |
| ); | |
| res.json({ | |
| signedUrl, | |
| fileName: buildReportDownloadName(job.input_file_name), | |
| expiresIn: 300, | |
| expiresAt: new Date(Date.now() + 300 * 1000).toISOString(), | |
| }); | |
| } catch (err: unknown) { | |
| const message = err instanceof Error ? err.message : String(err); | |
| logger.error('Failed to create report signed URL', { | |
| jobId, | |
| userId: authReq.userId, | |
| error: message, | |
| }); | |
| res.status(500).json({ error: 'Failed to create report download URL' }); | |
| } | |
| }, | |
| ); | |
| /** | |
| * GET /api/jobs/:jobId/receipt-url | |
| * | |
| * Creates a short-lived signed URL for a legacy Digital Receipt PDF. | |
| */ | |
| router.get( | |
| '/api/jobs/:jobId/receipt-url', | |
| authenticateUser, | |
| async (req, res: Response): Promise<void> => { | |
| const authReq = req as AuthenticatedRequest; | |
| const jobId = String(authReq.params.jobId || ''); | |
| try { | |
| const job = await getJobById(jobId); | |
| if (!job) { | |
| res.status(404).json({ error: 'Job not found' }); | |
| return; | |
| } | |
| if (job.user_id !== authReq.userId) { | |
| res.status(403).json({ error: 'Forbidden' }); | |
| return; | |
| } | |
| if (!job.receipt_pdf_path) { | |
| res.status(404).json({ error: 'Digital Receipt is not available yet' }); | |
| return; | |
| } | |
| if ( | |
| job.receipt_pdf_expires_at && | |
| new Date(job.receipt_pdf_expires_at).getTime() < Date.now() | |
| ) { | |
| res.status(410).json({ error: 'Digital Receipt has expired' }); | |
| return; | |
| } | |
| const fileName = buildReceiptDownloadName(job.input_file_name); | |
| const signedUrl = await createSignedUrl( | |
| config.reportBucket, | |
| job.receipt_pdf_path, | |
| 300, | |
| fileName, | |
| ); | |
| res.json({ | |
| signedUrl, | |
| fileName, | |
| expiresIn: 300, | |
| expiresAt: new Date(Date.now() + 300 * 1000).toISOString(), | |
| }); | |
| } catch (err: unknown) { | |
| const message = err instanceof Error ? err.message : String(err); | |
| logger.error('Failed to create Digital Receipt signed URL', { | |
| jobId, | |
| userId: authReq.userId, | |
| error: message, | |
| }); | |
| res.status(500).json({ error: 'Failed to create Digital Receipt download URL' }); | |
| } | |
| }, | |
| ); | |
| export default router; | |