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
92import { event, graphqlQuery } from "#src/utils";
const BATCH_SIZE = 1000;
const csvEscape = (val: any) => {
const str = val === null || val === undefined ? "" : String(val);
return `"${str.replace(/"/g, '""')}"`;
};
const extractHref = (content: string): string => {
const match = content?.match(/href=["']([^"']+)["']/i);
return match ? match[1] : "";
};
export const exportAuditTablePdfSourceLinks = async () => {
const auditId = (event.queryStringParameters as any).id;
const scanQuery = {
query: `query ($audit_id: uuid!) {
audits_by_pk(id: $audit_id) {
name
scans(order_by: {created_at: desc}, limit: 1) {
id
}
}
}`,
variables: { audit_id: auditId },
};
const scanResp = await graphqlQuery(scanQuery);
const auditName = scanResp.audits_by_pk?.name;
const latestScanId = scanResp.audits_by_pk?.scans?.[0]?.id;
const datePart = new Date().toISOString().split("T")[0];
const safeName = auditName ? auditName.replace(/[^a-z0-9-_]/gi, "_") + "-" : "";
const filename = `pdf-links-${safeName}${auditId}-${datePart}.csv`;
const emptyResponse = {
statusCode: 200,
headers: {
"content-type": "text/csv; charset=utf-8",
"content-disposition": `attachment; filename="${filename}"`,
},
body: "Source URL,PDF Link\n",
};
if (!latestScanId) return emptyResponse;
const scopedWhere = {
_and: [
{ scan_id: { _eq: latestScanId } },
{ url: { type: { _eq: "html" } } },
{ blocker_messages: { message: { category: { _eq: "pdf-link" } } } },
],
};
const rows: string[] = [];
let offset = 0;
while (true) {
const batchQuery = {
query: `query ($limit: Int!, $offset: Int!, $where: blockers_bool_exp!) {
blockers(where: $where, limit: $limit, offset: $offset) {
content
url { url }
}
}`,
variables: { limit: BATCH_SIZE, offset, where: scopedWhere },
};
const batchResp = await graphqlQuery(batchQuery);
const batch: any[] = batchResp.blockers || [];
for (const blocker of batch) {
const sourceUrl = blocker.url?.url || "";
const pdfLink = extractHref(blocker.content);
rows.push([csvEscape(sourceUrl), csvEscape(pdfLink)].join(","));
}
if (batch.length < BATCH_SIZE) break;
offset += BATCH_SIZE;
}
const csv = ["Source URL,PDF Link", ...rows].join("\n");
return {
statusCode: 200,
headers: {
"content-type": "text/csv; charset=utf-8",
"content-disposition": `attachment; filename="${filename}"`,
},
body: csv,
};
};