Skip to content

Commit 3704688

Browse files
fix(scan): insert remote CSV URLs via SQL with explicit user_id
1 parent 30121fa commit 3704688

2 files changed

Lines changed: 59 additions & 52 deletions

File tree

apps/backend/routes/internal/syncAuditUrlsFromRemoteCsv.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export const syncAuditUrlsFromRemoteCsv = async (auditId: string) => {
3030
values: [auditId],
3131
})
3232
).rows?.[0];
33-
const remoteCsvUrl = audit.remote_csv_url;
33+
const remoteCsvUrl = audit?.remote_csv_url;
3434

35-
if (!audit.remote_csv_url.trim()) {
35+
if (!audit?.remote_csv_url?.trim()) {
3636
// doesn't use remote csv, skip
3737
console.log(`Audit ${auditId} doesn't use remote CSV, skipping sync`);
3838
return;
@@ -57,7 +57,6 @@ export const syncAuditUrlsFromRemoteCsv = async (auditId: string) => {
5757
values: [auditId],
5858
})
5959
).rows as DBUrl[];
60-
await db.clean();
6160

6261
// cache the url objects for efficiency and also why not
6362
const existingKeys = new Set(
@@ -98,17 +97,20 @@ export const syncAuditUrlsFromRemoteCsv = async (auditId: string) => {
9897
// Store updates in db
9998
//
10099

101-
// new URLs
102-
for (const url of urlsToAdd) {
103-
await graphqlQuery({
104-
query: `mutation ($audit_id: uuid, $url: String, $type: String) {
105-
insert_urls_one(object: {audit_id: $audit_id, url: $url, type: $type}) {id}
106-
}`,
107-
variables: {
108-
audit_id: auditId,
109-
url: url.url,
110-
type: url.type,
111-
},
100+
// new URLs — inserted via SQL with an explicit user_id. The previous GraphQL
101+
// mutation relied on Hasura's user-role column preset to fill user_id, which only
102+
// exists when a user JWT is forwarded; from the scheduled path (no HTTP headers)
103+
// it ran as admin and every insert failed the urls.user_id NOT NULL constraint.
104+
if (urlsToAdd.length > 0) {
105+
await db.query({
106+
text: `INSERT INTO "urls" ("user_id", "audit_id", "url", "type")
107+
SELECT * FROM UNNEST($1::uuid[], $2::uuid[], $3::text[], $4::text[])`,
108+
values: [
109+
urlsToAdd.map(() => audit.user_id),
110+
urlsToAdd.map(() => auditId),
111+
urlsToAdd.map((url: newUrl) => url.url),
112+
urlsToAdd.map((url: newUrl) => url.type),
113+
],
112114
});
113115
}
114116

apps/backend/routes/scheduled/runEveryMinute.ts

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -50,48 +50,53 @@ export const runEveryMinute = async () => {
5050
})
5151
).rows.map((obj: { id: string; }) => obj.id);
5252
for (const scheduledAuditId of scheduledAuditIds) {
53-
// hook to check for remote CSV
54-
await syncAuditUrlsFromRemoteCsv(scheduledAuditId);
53+
// isolate each audit so one failure can't starve the rest of the batch
54+
try {
55+
// hook to check for remote CSV
56+
await syncAuditUrlsFromRemoteCsv(scheduledAuditId);
5557

56-
const urls = (
57-
await db.query({
58-
text: `SELECT * FROM "urls" WHERE "audit_id"=$1`,
59-
values: [scheduledAuditId],
60-
})
61-
).rows;
58+
const urls = (
59+
await db.query({
60+
text: `SELECT * FROM "urls" WHERE "audit_id"=$1`,
61+
values: [scheduledAuditId],
62+
})
63+
).rows;
6264

63-
// Skip scheduled audits with no URLs to prevent hung scans
64-
if (!urls || urls.length === 0) {
65-
console.log("Skipping scheduled audit with no URLs:", scheduledAuditId);
66-
continue;
67-
}
65+
// Skip scheduled audits with no URLs to prevent hung scans
66+
if (!urls || urls.length === 0) {
67+
console.log("Skipping scheduled audit with no URLs:", scheduledAuditId);
68+
continue;
69+
}
6870

69-
const scanId = (
70-
await db.query({
71-
text: `INSERT INTO "scans" ("audit_id", "status", "pages") VALUES ($1, $2, $3) RETURNING "id"`,
72-
values: [
73-
scheduledAuditId,
74-
"processing",
75-
JSON.stringify(urls.map((obj: { url: string; type: string; }) => ({ url: obj.url, type: obj.type }))),
76-
],
77-
})
78-
).rows[0].id;
79-
await lambda.send(
80-
new InvokeCommand({
81-
FunctionName: process.env.SQS_ROUTER_FUNCTION_NAME ?? "aws-lambda-scan-sqs-router",
82-
InvocationType: "Event",
83-
Payload: JSON.stringify({
84-
urls: urls?.map((url: { id: string; url: string; type: string; }) => ({
85-
auditId: scheduledAuditId,
86-
scanId: scanId,
87-
urlId: url.id,
88-
url: url.url,
89-
type: url.type,
90-
})),
71+
const scanId = (
72+
await db.query({
73+
text: `INSERT INTO "scans" ("audit_id", "status", "pages") VALUES ($1, $2, $3) RETURNING "id"`,
74+
values: [
75+
scheduledAuditId,
76+
"processing",
77+
JSON.stringify(urls.map((obj: { url: string; type: string; }) => ({ url: obj.url, type: obj.type }))),
78+
],
79+
})
80+
).rows[0].id;
81+
await lambda.send(
82+
new InvokeCommand({
83+
FunctionName: process.env.SQS_ROUTER_FUNCTION_NAME ?? "aws-lambda-scan-sqs-router",
84+
InvocationType: "Event",
85+
Payload: JSON.stringify({
86+
urls: urls?.map((url: { id: string; url: string; type: string; }) => ({
87+
auditId: scheduledAuditId,
88+
scanId: scanId,
89+
urlId: url.id,
90+
url: url.url,
91+
type: url.type,
92+
})),
93+
}),
9194
}),
92-
}),
93-
);
94-
console.log("Scan jobs queued for audit:", scheduledAuditId);
95+
);
96+
console.log("Scan jobs queued for audit:", scheduledAuditId);
97+
} catch (error) {
98+
console.error("Scheduled scan failed for audit:", scheduledAuditId, error);
99+
}
95100
}
96101

97102
// See if there are any "stuck" scans that we should error out!

0 commit comments

Comments
 (0)