Spaces:
Sleeping
Sleeping
File size: 4,532 Bytes
9e212e3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | 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;
|