@canonry/canonry 4.117.1 → 4.117.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +21 -0
- package/assets/assets/{BacklinksPage-Bkafm10m.js → BacklinksPage-CschVQxC.js} +1 -1
- package/assets/assets/{ChartPrimitives-NV_GgqFn.js → ChartPrimitives-D4782Ifx.js} +1 -1
- package/assets/assets/ProjectPage-BSGTpBUJ.js +7 -0
- package/assets/assets/{RunRow-df0d24S6.js → RunRow-ggNGlwdN.js} +1 -1
- package/assets/assets/{RunsPage-CU6-4YTM.js → RunsPage-Bfp0K6Ry.js} +1 -1
- package/assets/assets/{SettingsPage-BKw6B6cd.js → SettingsPage-Cy9SlBJX.js} +1 -1
- package/assets/assets/{TrafficPage-CzpiYHKj.js → TrafficPage-1S2PzSHB.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CC5nuqM1.js → TrafficSourceDetailPage-CqXCoaod.js} +1 -1
- package/assets/assets/{arrow-left-CbQcW2pL.js → arrow-left-kCK5M8Bp.js} +1 -1
- package/assets/assets/{extract-error-message-CvqIZ-le.js → extract-error-message-C82LAW6q.js} +1 -1
- package/assets/assets/index-DrDNyUhv.css +1 -0
- package/assets/assets/{index-LjcWHaNZ.js → index-i-t64RXw.js} +74 -74
- package/assets/assets/{trash-2-3J2u0woR.js → trash-2-8Pe4v1T0.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-BMBHUFMI.js → chunk-4PMMEFME.js} +42 -1
- package/dist/{chunk-V4SW7SVX.js → chunk-BMPIV35C.js} +696 -417
- package/dist/{chunk-IAJ5TQ4S.js → chunk-EZME2J2G.js} +1196 -1140
- package/dist/{chunk-NY7SC4ES.js → chunk-F2SRZJMY.js} +4 -4
- package/dist/cli.js +121 -77
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-C5YLASIP.js → intelligence-service-GNOE4J35.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +13 -13
- package/assets/assets/ProjectPage-0-NnRxys.js +0 -7
- package/assets/assets/index-B4R1-8hR.css +0 -1
|
@@ -220,11 +220,14 @@ import {
|
|
|
220
220
|
resolveConfigSpecQueries,
|
|
221
221
|
resolveLocations,
|
|
222
222
|
resolveSnapshotRequestQueries,
|
|
223
|
+
resultsExportDtoSchema,
|
|
223
224
|
runDetailDtoSchema,
|
|
224
225
|
runDtoSchema,
|
|
225
226
|
runInProgress,
|
|
226
227
|
runNotCancellable,
|
|
228
|
+
runStatusSchema,
|
|
227
229
|
runTriggerRequestSchema,
|
|
230
|
+
runTriggerSchema,
|
|
228
231
|
runtimeStateMissing,
|
|
229
232
|
schedulableRunKindSchema,
|
|
230
233
|
scheduleDtoSchema,
|
|
@@ -280,10 +283,10 @@ import {
|
|
|
280
283
|
wordpressSchemaDeployResultDtoSchema,
|
|
281
284
|
wordpressSchemaStatusResultDtoSchema,
|
|
282
285
|
wordpressStatusDtoSchema
|
|
283
|
-
} from "./chunk-
|
|
286
|
+
} from "./chunk-EZME2J2G.js";
|
|
284
287
|
|
|
285
288
|
// src/intelligence-service.ts
|
|
286
|
-
import { eq as
|
|
289
|
+
import { eq as eq40, desc as desc18, asc as asc7, and as and31, ne as ne5, or as or6, inArray as inArray15, gte as gte8, lte as lte5 } from "drizzle-orm";
|
|
287
290
|
|
|
288
291
|
// ../db/src/client.ts
|
|
289
292
|
import { mkdirSync } from "fs";
|
|
@@ -14239,8 +14242,226 @@ async function visibilityStatsRoutes(app) {
|
|
|
14239
14242
|
});
|
|
14240
14243
|
}
|
|
14241
14244
|
|
|
14245
|
+
// ../api-routes/src/results-export.ts
|
|
14246
|
+
import { and as and15, asc as asc3, eq as eq19, gte as gte3, lte as lte2 } from "drizzle-orm";
|
|
14247
|
+
var CSV_COLUMNS = [
|
|
14248
|
+
"export_schema_version",
|
|
14249
|
+
"project_id",
|
|
14250
|
+
"project_name",
|
|
14251
|
+
"project_display_name",
|
|
14252
|
+
"canonical_domain",
|
|
14253
|
+
"run_id",
|
|
14254
|
+
"run_kind",
|
|
14255
|
+
"run_status",
|
|
14256
|
+
"run_trigger",
|
|
14257
|
+
"run_created_at",
|
|
14258
|
+
"run_started_at",
|
|
14259
|
+
"run_finished_at",
|
|
14260
|
+
"snapshot_id",
|
|
14261
|
+
"snapshot_created_at",
|
|
14262
|
+
"query_id",
|
|
14263
|
+
"query",
|
|
14264
|
+
"provider",
|
|
14265
|
+
"model",
|
|
14266
|
+
"location",
|
|
14267
|
+
"citation_state",
|
|
14268
|
+
"cited",
|
|
14269
|
+
"answer_mentioned",
|
|
14270
|
+
"mention_state",
|
|
14271
|
+
"cited_domains_json",
|
|
14272
|
+
"competitor_overlap_json",
|
|
14273
|
+
"recommended_competitors_json",
|
|
14274
|
+
"answer_text",
|
|
14275
|
+
"grounding_sources_json",
|
|
14276
|
+
"search_queries_json"
|
|
14277
|
+
];
|
|
14278
|
+
function parseFormat(value) {
|
|
14279
|
+
if (value === void 0 || value === "" || value === "json") return "json";
|
|
14280
|
+
if (value === "csv") return "csv";
|
|
14281
|
+
throw validationError('"format" must be "json" or "csv"');
|
|
14282
|
+
}
|
|
14283
|
+
function parseBoolean(value, name) {
|
|
14284
|
+
if (value === void 0 || value === "") return false;
|
|
14285
|
+
if (value === "true" || value === "1") return true;
|
|
14286
|
+
if (value === "false" || value === "0") return false;
|
|
14287
|
+
throw validationError(`"${name}" must be "true" or "false"`);
|
|
14288
|
+
}
|
|
14289
|
+
function parseDateBounds(since, until) {
|
|
14290
|
+
let sinceIso = null;
|
|
14291
|
+
let untilIso = null;
|
|
14292
|
+
if (since !== void 0 && since !== "") {
|
|
14293
|
+
const ms = Date.parse(since);
|
|
14294
|
+
if (Number.isNaN(ms)) throw validationError('"since" must be an ISO 8601 date/time');
|
|
14295
|
+
sinceIso = new Date(ms).toISOString();
|
|
14296
|
+
}
|
|
14297
|
+
if (until !== void 0 && until !== "") {
|
|
14298
|
+
const ms = parseInclusiveEndMs(until);
|
|
14299
|
+
if (ms === null) throw validationError('"until" must be an ISO 8601 date/time');
|
|
14300
|
+
untilIso = new Date(ms).toISOString();
|
|
14301
|
+
}
|
|
14302
|
+
if (sinceIso !== null && untilIso !== null && untilIso < sinceIso) {
|
|
14303
|
+
throw validationError('"until" must be on or after "since"');
|
|
14304
|
+
}
|
|
14305
|
+
return { sinceIso, untilIso };
|
|
14306
|
+
}
|
|
14307
|
+
function readEvidence(rawResponse) {
|
|
14308
|
+
const parsed = parseJsonColumn(rawResponse, {});
|
|
14309
|
+
const groundingSources = Array.isArray(parsed.groundingSources) ? parsed.groundingSources.filter(
|
|
14310
|
+
(source) => typeof source === "object" && source !== null && typeof source.uri === "string"
|
|
14311
|
+
).map((source) => ({ uri: source.uri, title: typeof source.title === "string" ? source.title : "" })) : [];
|
|
14312
|
+
const searchQueries = Array.isArray(parsed.searchQueries) ? parsed.searchQueries.filter((query) => typeof query === "string") : [];
|
|
14313
|
+
return { groundingSources, searchQueries };
|
|
14314
|
+
}
|
|
14315
|
+
function recordMentionState(answerMentioned) {
|
|
14316
|
+
if (answerMentioned === null) return null;
|
|
14317
|
+
return answerMentioned ? "mentioned" : "not-mentioned";
|
|
14318
|
+
}
|
|
14319
|
+
function spreadsheetSafe(value) {
|
|
14320
|
+
return /^[\t\r ]*[=+\-@]/.test(value) ? `'${value}` : value;
|
|
14321
|
+
}
|
|
14322
|
+
function csvCell(value) {
|
|
14323
|
+
if (value === null) return "";
|
|
14324
|
+
const text2 = typeof value === "string" ? spreadsheetSafe(value) : String(value);
|
|
14325
|
+
return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
|
|
14326
|
+
}
|
|
14327
|
+
function renderResultsExportCsv(report) {
|
|
14328
|
+
const rows = report.records.map((record) => [
|
|
14329
|
+
report.schemaVersion,
|
|
14330
|
+
report.project.id,
|
|
14331
|
+
report.project.name,
|
|
14332
|
+
report.project.displayName,
|
|
14333
|
+
report.project.canonicalDomain,
|
|
14334
|
+
record.runId,
|
|
14335
|
+
record.runKind,
|
|
14336
|
+
record.runStatus,
|
|
14337
|
+
record.runTrigger,
|
|
14338
|
+
record.runCreatedAt,
|
|
14339
|
+
record.runStartedAt,
|
|
14340
|
+
record.runFinishedAt,
|
|
14341
|
+
record.snapshotId,
|
|
14342
|
+
record.snapshotCreatedAt,
|
|
14343
|
+
record.queryId,
|
|
14344
|
+
record.query,
|
|
14345
|
+
record.provider,
|
|
14346
|
+
record.model,
|
|
14347
|
+
record.location,
|
|
14348
|
+
record.citationState,
|
|
14349
|
+
record.cited,
|
|
14350
|
+
record.answerMentioned,
|
|
14351
|
+
record.mentionState,
|
|
14352
|
+
JSON.stringify(record.citedDomains),
|
|
14353
|
+
JSON.stringify(record.competitorOverlap),
|
|
14354
|
+
JSON.stringify(record.recommendedCompetitors),
|
|
14355
|
+
record.answerText,
|
|
14356
|
+
JSON.stringify(record.groundingSources),
|
|
14357
|
+
JSON.stringify(record.searchQueries)
|
|
14358
|
+
].map(csvCell).join(","));
|
|
14359
|
+
return [CSV_COLUMNS.join(","), ...rows].join("\r\n") + "\r\n";
|
|
14360
|
+
}
|
|
14361
|
+
function filenameFor(projectName, format) {
|
|
14362
|
+
const safeName = projectName.replace(/[^\w.-]+/gi, "_") || "project";
|
|
14363
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
14364
|
+
return `canonry-results-${safeName}-${date}.${format}`;
|
|
14365
|
+
}
|
|
14366
|
+
async function resultsExportRoutes(app) {
|
|
14367
|
+
app.get("/projects/:name/results/export", async (request, reply) => {
|
|
14368
|
+
const project = resolveProject(app.db, request.params.name);
|
|
14369
|
+
const format = parseFormat(request.query.format);
|
|
14370
|
+
const includeProbes = parseBoolean(request.query.includeProbes, "includeProbes");
|
|
14371
|
+
const { sinceIso, untilIso } = parseDateBounds(request.query.since, request.query.until);
|
|
14372
|
+
const conditions = [
|
|
14373
|
+
eq19(runs.projectId, project.id),
|
|
14374
|
+
eq19(runs.kind, RunKinds["answer-visibility"]),
|
|
14375
|
+
...includeProbes ? [] : [notProbeRun()],
|
|
14376
|
+
...sinceIso ? [gte3(runs.createdAt, sinceIso)] : [],
|
|
14377
|
+
...untilIso ? [lte2(runs.createdAt, untilIso)] : []
|
|
14378
|
+
];
|
|
14379
|
+
const rows = app.db.select({
|
|
14380
|
+
runId: runs.id,
|
|
14381
|
+
runStatus: runs.status,
|
|
14382
|
+
runTrigger: runs.trigger,
|
|
14383
|
+
runCreatedAt: runs.createdAt,
|
|
14384
|
+
runStartedAt: runs.startedAt,
|
|
14385
|
+
runFinishedAt: runs.finishedAt,
|
|
14386
|
+
snapshotId: querySnapshots.id,
|
|
14387
|
+
snapshotCreatedAt: querySnapshots.createdAt,
|
|
14388
|
+
queryId: querySnapshots.queryId,
|
|
14389
|
+
queryText: querySnapshots.queryText,
|
|
14390
|
+
currentQuery: queries.query,
|
|
14391
|
+
provider: querySnapshots.provider,
|
|
14392
|
+
model: querySnapshots.model,
|
|
14393
|
+
location: querySnapshots.location,
|
|
14394
|
+
citationState: querySnapshots.citationState,
|
|
14395
|
+
answerMentioned: querySnapshots.answerMentioned,
|
|
14396
|
+
citedDomains: querySnapshots.citedDomains,
|
|
14397
|
+
competitorOverlap: querySnapshots.competitorOverlap,
|
|
14398
|
+
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
14399
|
+
answerText: querySnapshots.answerText,
|
|
14400
|
+
rawResponse: querySnapshots.rawResponse
|
|
14401
|
+
}).from(querySnapshots).innerJoin(runs, eq19(querySnapshots.runId, runs.id)).leftJoin(queries, eq19(querySnapshots.queryId, queries.id)).where(and15(...conditions)).orderBy(asc3(runs.createdAt), asc3(runs.id), asc3(querySnapshots.createdAt), asc3(querySnapshots.id)).all();
|
|
14402
|
+
const records = rows.map((row) => {
|
|
14403
|
+
const evidence = readEvidence(row.rawResponse);
|
|
14404
|
+
const citationState = row.citationState === CitationStates.cited ? CitationStates.cited : CitationStates["not-cited"];
|
|
14405
|
+
return {
|
|
14406
|
+
runId: row.runId,
|
|
14407
|
+
runKind: RunKinds["answer-visibility"],
|
|
14408
|
+
runStatus: runStatusSchema.parse(row.runStatus),
|
|
14409
|
+
runTrigger: runTriggerSchema.parse(row.runTrigger),
|
|
14410
|
+
runCreatedAt: row.runCreatedAt,
|
|
14411
|
+
runStartedAt: row.runStartedAt,
|
|
14412
|
+
runFinishedAt: row.runFinishedAt,
|
|
14413
|
+
snapshotId: row.snapshotId,
|
|
14414
|
+
snapshotCreatedAt: row.snapshotCreatedAt,
|
|
14415
|
+
queryId: row.queryId,
|
|
14416
|
+
query: row.queryText ?? row.currentQuery,
|
|
14417
|
+
provider: row.provider,
|
|
14418
|
+
model: row.model,
|
|
14419
|
+
location: row.location,
|
|
14420
|
+
citationState,
|
|
14421
|
+
cited: citationState === CitationStates.cited,
|
|
14422
|
+
answerMentioned: row.answerMentioned,
|
|
14423
|
+
mentionState: recordMentionState(row.answerMentioned),
|
|
14424
|
+
citedDomains: row.citedDomains,
|
|
14425
|
+
competitorOverlap: row.competitorOverlap,
|
|
14426
|
+
recommendedCompetitors: row.recommendedCompetitors,
|
|
14427
|
+
answerText: row.answerText,
|
|
14428
|
+
groundingSources: evidence.groundingSources,
|
|
14429
|
+
searchQueries: evidence.searchQueries
|
|
14430
|
+
};
|
|
14431
|
+
});
|
|
14432
|
+
const dto = {
|
|
14433
|
+
schemaVersion: "canonry.results-export/v1",
|
|
14434
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14435
|
+
project: {
|
|
14436
|
+
id: project.id,
|
|
14437
|
+
name: project.name,
|
|
14438
|
+
displayName: project.displayName,
|
|
14439
|
+
canonicalDomain: project.canonicalDomain,
|
|
14440
|
+
country: project.country,
|
|
14441
|
+
language: project.language
|
|
14442
|
+
},
|
|
14443
|
+
filters: {
|
|
14444
|
+
since: request.query.since || null,
|
|
14445
|
+
until: request.query.until || null,
|
|
14446
|
+
includeProbes
|
|
14447
|
+
},
|
|
14448
|
+
recordCount: records.length,
|
|
14449
|
+
records
|
|
14450
|
+
};
|
|
14451
|
+
reply.header("Content-Disposition", `attachment; filename="${filenameFor(project.name, format)}"`);
|
|
14452
|
+
reply.header("Cache-Control", "private, no-store");
|
|
14453
|
+
reply.header("X-Content-Type-Options", "nosniff");
|
|
14454
|
+
if (format === "csv") {
|
|
14455
|
+
reply.type("text/csv; charset=utf-8");
|
|
14456
|
+
return reply.send(renderResultsExportCsv(dto));
|
|
14457
|
+
}
|
|
14458
|
+
reply.type("application/json; charset=utf-8");
|
|
14459
|
+
return reply.send(dto);
|
|
14460
|
+
});
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14242
14463
|
// ../api-routes/src/composites.ts
|
|
14243
|
-
import { eq as
|
|
14464
|
+
import { eq as eq20, and as and16, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray10 } from "drizzle-orm";
|
|
14244
14465
|
var TOP_INSIGHT_LIMIT = 5;
|
|
14245
14466
|
var SEARCH_HIT_HARD_LIMIT = 50;
|
|
14246
14467
|
var SEARCH_SNIPPET_RADIUS = 80;
|
|
@@ -14258,7 +14479,7 @@ async function compositeRoutes(app) {
|
|
|
14258
14479
|
const project = resolveProject(app.db, request.params.name);
|
|
14259
14480
|
const filterLocation = (request.query.location ?? "").trim() || null;
|
|
14260
14481
|
const sinceIso = parseSinceFilter(request.query.since);
|
|
14261
|
-
const allRunsRaw = app.db.select().from(runs).where(
|
|
14482
|
+
const allRunsRaw = app.db.select().from(runs).where(and16(eq20(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
|
|
14262
14483
|
const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
|
|
14263
14484
|
const totalRuns = allRuns.length;
|
|
14264
14485
|
const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
|
|
@@ -14271,15 +14492,15 @@ async function compositeRoutes(app) {
|
|
|
14271
14492
|
const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
|
|
14272
14493
|
const latestRunRow = allRuns[0] ?? null;
|
|
14273
14494
|
const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
|
|
14274
|
-
const healthRow = app.db.select().from(healthSnapshots).where(
|
|
14495
|
+
const healthRow = app.db.select().from(healthSnapshots).where(eq20(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
|
|
14275
14496
|
const health = healthRow ? mapHealthRow2(healthRow) : null;
|
|
14276
|
-
const insightRows = app.db.select().from(insights).where(
|
|
14497
|
+
const insightRows = app.db.select().from(insights).where(eq20(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
|
|
14277
14498
|
const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
|
|
14278
14499
|
const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
|
|
14279
14500
|
const snapshotRunIds = new Set(sparklineRunIds);
|
|
14280
14501
|
for (const run of latestVisRunGroup) snapshotRunIds.add(run.id);
|
|
14281
14502
|
for (const run of previousVisRunGroup) snapshotRunIds.add(run.id);
|
|
14282
|
-
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
14503
|
+
const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq20(queries.projectId, project.id)).all();
|
|
14283
14504
|
const queryIdByText = new Map(projectQueries.map((q) => [normalizeQueryText(q.query), q.id]));
|
|
14284
14505
|
const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds], queryIdByText);
|
|
14285
14506
|
const latestSnapshots = latestVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
|
|
@@ -14295,7 +14516,7 @@ async function compositeRoutes(app) {
|
|
|
14295
14516
|
trackedPrevious,
|
|
14296
14517
|
previousVisibilityRun?.createdAt ?? null
|
|
14297
14518
|
);
|
|
14298
|
-
const competitorRows = app.db.select().from(competitors).where(
|
|
14519
|
+
const competitorRows = app.db.select().from(competitors).where(eq20(competitors.projectId, project.id)).all();
|
|
14299
14520
|
const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
|
|
14300
14521
|
for (const snapshots of snapshotsByRun.values()) {
|
|
14301
14522
|
for (const snapshot of snapshots) {
|
|
@@ -14413,9 +14634,9 @@ async function compositeRoutes(app) {
|
|
|
14413
14634
|
citedDomains: querySnapshots.citedDomains,
|
|
14414
14635
|
rawResponse: querySnapshots.rawResponse,
|
|
14415
14636
|
createdAt: querySnapshots.createdAt
|
|
14416
|
-
}).from(querySnapshots).innerJoin(queries,
|
|
14417
|
-
|
|
14418
|
-
|
|
14637
|
+
}).from(querySnapshots).innerJoin(queries, eq20(querySnapshots.queryId, queries.id)).where(
|
|
14638
|
+
and16(
|
|
14639
|
+
eq20(queries.projectId, project.id),
|
|
14419
14640
|
or4(
|
|
14420
14641
|
sql8`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
|
|
14421
14642
|
sql8`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
|
|
@@ -14425,8 +14646,8 @@ async function compositeRoutes(app) {
|
|
|
14425
14646
|
)
|
|
14426
14647
|
).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
|
|
14427
14648
|
const insightMatches = app.db.select().from(insights).where(
|
|
14428
|
-
|
|
14429
|
-
|
|
14649
|
+
and16(
|
|
14650
|
+
eq20(insights.projectId, project.id),
|
|
14430
14651
|
or4(
|
|
14431
14652
|
like2(insights.title, pattern),
|
|
14432
14653
|
like2(insights.query, pattern),
|
|
@@ -14642,8 +14863,8 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
|
|
|
14642
14863
|
// NULLIF guards the degenerate impressions=0 case (SQLite returns NULL,
|
|
14643
14864
|
// which the JS coerces to 0 — caught by the impression floor anyway).
|
|
14644
14865
|
avgPosition: sql8`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
|
|
14645
|
-
}).from(gscSearchData).where(
|
|
14646
|
-
|
|
14866
|
+
}).from(gscSearchData).where(and16(
|
|
14867
|
+
eq20(gscSearchData.projectId, projectId),
|
|
14647
14868
|
sql8`${gscSearchData.date} >= ${cutoff}`,
|
|
14648
14869
|
sql8`${gscSearchData.impressions} > 0`
|
|
14649
14870
|
)).groupBy(gscSearchData.query).orderBy(sql8`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
|
|
@@ -14666,8 +14887,8 @@ function buildIndexCoverageScore(app, projectId) {
|
|
|
14666
14887
|
tooltip,
|
|
14667
14888
|
trend: []
|
|
14668
14889
|
};
|
|
14669
|
-
const gscRow = app.db.select().from(gscCoverageSnapshots).where(
|
|
14670
|
-
const bingRow = app.db.select().from(bingCoverageSnapshots).where(
|
|
14890
|
+
const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq20(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
|
|
14891
|
+
const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq20(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
|
|
14671
14892
|
const chosen = pickIndexCoverageRow(gscRow, bingRow);
|
|
14672
14893
|
if (!chosen) return empty;
|
|
14673
14894
|
const total = chosen.indexed + chosen.notIndexed;
|
|
@@ -14693,7 +14914,7 @@ function countGoogleDeindexedUrls(app, projectId) {
|
|
|
14693
14914
|
url: gscUrlInspections.url,
|
|
14694
14915
|
indexingState: gscUrlInspections.indexingState,
|
|
14695
14916
|
inspectedAt: gscUrlInspections.inspectedAt
|
|
14696
|
-
}).from(gscUrlInspections).where(
|
|
14917
|
+
}).from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
|
|
14697
14918
|
if (rows.length === 0) return 0;
|
|
14698
14919
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
14699
14920
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -15022,6 +15243,7 @@ var SCHEMA_TABLE = {
|
|
|
15022
15243
|
ProjectOverviewDto: projectOverviewDtoSchema,
|
|
15023
15244
|
ProjectReportDto: projectReportDtoSchema,
|
|
15024
15245
|
QueryDto: queryDtoSchema,
|
|
15246
|
+
ResultsExportDto: resultsExportDtoSchema,
|
|
15025
15247
|
RunDetailDto: runDetailDtoSchema,
|
|
15026
15248
|
RunDto: runDtoSchema,
|
|
15027
15249
|
SchedulableRunKind: schedulableRunKindSchema,
|
|
@@ -15526,6 +15748,62 @@ var routeCatalog = [
|
|
|
15526
15748
|
404: errorResponse("Project not found.")
|
|
15527
15749
|
}
|
|
15528
15750
|
},
|
|
15751
|
+
{
|
|
15752
|
+
method: "get",
|
|
15753
|
+
path: "/api/v1/projects/{name}/results/export",
|
|
15754
|
+
summary: "Download historical answer-engine results",
|
|
15755
|
+
tags: ["exports"],
|
|
15756
|
+
description: "Downloads one record per persisted answer-visibility query \xD7 provider observation. Citation and mention stay independent: `citationState` describes source-list attribution, while nullable `answerMentioned` describes answer-text presence. Excludes probe runs by default. JSON is a versioned portable artifact; CSV is its flat spreadsheet representation. Raw provider payloads, credentials, and local paths are never included.",
|
|
15757
|
+
parameters: [
|
|
15758
|
+
nameParameter,
|
|
15759
|
+
{
|
|
15760
|
+
name: "format",
|
|
15761
|
+
in: "query",
|
|
15762
|
+
description: "Download format. Defaults to `json`.",
|
|
15763
|
+
schema: { type: "string", enum: ["json", "csv"], default: "json" }
|
|
15764
|
+
},
|
|
15765
|
+
{
|
|
15766
|
+
name: "since",
|
|
15767
|
+
in: "query",
|
|
15768
|
+
description: "Inclusive ISO 8601 lower bound on run creation time.",
|
|
15769
|
+
schema: stringSchema
|
|
15770
|
+
},
|
|
15771
|
+
{
|
|
15772
|
+
name: "until",
|
|
15773
|
+
in: "query",
|
|
15774
|
+
description: "Inclusive ISO 8601 upper bound on run creation time. A date-only value includes that whole UTC day.",
|
|
15775
|
+
schema: stringSchema
|
|
15776
|
+
},
|
|
15777
|
+
{
|
|
15778
|
+
name: "includeProbes",
|
|
15779
|
+
in: "query",
|
|
15780
|
+
description: "Include operator/agent probe runs. Defaults to `false`.",
|
|
15781
|
+
schema: booleanSchema
|
|
15782
|
+
}
|
|
15783
|
+
],
|
|
15784
|
+
responses: {
|
|
15785
|
+
200: {
|
|
15786
|
+
description: "Results attachment returned.",
|
|
15787
|
+
content: {
|
|
15788
|
+
// The query-selected attachment can be JSON or CSV. OpenAPI cannot
|
|
15789
|
+
// express that the `format` parameter selects a media type, so keep
|
|
15790
|
+
// the generated SDK honest with a union rather than promising JSON
|
|
15791
|
+
// to a caller that requested CSV.
|
|
15792
|
+
"application/json": {
|
|
15793
|
+
schema: {
|
|
15794
|
+
oneOf: [
|
|
15795
|
+
{ $ref: "#/components/schemas/ResultsExportDto" },
|
|
15796
|
+
{ type: "string" }
|
|
15797
|
+
]
|
|
15798
|
+
}
|
|
15799
|
+
},
|
|
15800
|
+
"text/csv": { schema: { type: "string" } }
|
|
15801
|
+
}
|
|
15802
|
+
},
|
|
15803
|
+
400: errorResponse("Invalid export filters."),
|
|
15804
|
+
404: errorResponse("Project not found.")
|
|
15805
|
+
}
|
|
15806
|
+
},
|
|
15529
15807
|
{
|
|
15530
15808
|
method: "get",
|
|
15531
15809
|
path: "/api/v1/projects/{name}/queries",
|
|
@@ -19432,7 +19710,7 @@ async function settingsRoutes(app, opts) {
|
|
|
19432
19710
|
|
|
19433
19711
|
// ../api-routes/src/keys.ts
|
|
19434
19712
|
import crypto13 from "crypto";
|
|
19435
|
-
import { desc as desc10, eq as
|
|
19713
|
+
import { desc as desc10, eq as eq21 } from "drizzle-orm";
|
|
19436
19714
|
var KEYS_WRITE_SCOPE = "keys.write";
|
|
19437
19715
|
function toApiKeyDto(row) {
|
|
19438
19716
|
const scopes = Array.isArray(row.scopes) ? row.scopes : [];
|
|
@@ -19451,7 +19729,7 @@ function toApiKeyDto(row) {
|
|
|
19451
19729
|
async function keysRoutes(app) {
|
|
19452
19730
|
app.get("/keys", async (request) => {
|
|
19453
19731
|
const scopedProjectId = request.apiKey?.projectId;
|
|
19454
|
-
const rows = scopedProjectId ? app.db.select().from(apiKeys).where(
|
|
19732
|
+
const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq21(apiKeys.projectId, scopedProjectId)).orderBy(desc10(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
|
|
19455
19733
|
return { keys: rows.map(toApiKeyDto) };
|
|
19456
19734
|
});
|
|
19457
19735
|
app.get("/keys/self", async (request) => {
|
|
@@ -19459,7 +19737,7 @@ async function keysRoutes(app) {
|
|
|
19459
19737
|
if (!id) {
|
|
19460
19738
|
throw notFound("API key", "self");
|
|
19461
19739
|
}
|
|
19462
|
-
const row = app.db.select().from(apiKeys).where(
|
|
19740
|
+
const row = app.db.select().from(apiKeys).where(eq21(apiKeys.id, id)).get();
|
|
19463
19741
|
if (!row) {
|
|
19464
19742
|
throw notFound("API key", id);
|
|
19465
19743
|
}
|
|
@@ -19477,7 +19755,7 @@ async function keysRoutes(app) {
|
|
|
19477
19755
|
throw forbidden("A project-scoped key can only mint keys scoped to its own project.");
|
|
19478
19756
|
}
|
|
19479
19757
|
if (projectId) {
|
|
19480
|
-
const proj = app.db.select({ id: projects.id }).from(projects).where(
|
|
19758
|
+
const proj = app.db.select({ id: projects.id }).from(projects).where(eq21(projects.id, projectId)).get();
|
|
19481
19759
|
if (!proj) {
|
|
19482
19760
|
throw notFound("Project", projectId);
|
|
19483
19761
|
}
|
|
@@ -19523,7 +19801,7 @@ async function keysRoutes(app) {
|
|
|
19523
19801
|
app.post("/keys/:id/revoke", async (request) => {
|
|
19524
19802
|
requireScope(request, KEYS_WRITE_SCOPE);
|
|
19525
19803
|
const { id } = request.params;
|
|
19526
|
-
const row = app.db.select().from(apiKeys).where(
|
|
19804
|
+
const row = app.db.select().from(apiKeys).where(eq21(apiKeys.id, id)).get();
|
|
19527
19805
|
if (!row) {
|
|
19528
19806
|
throw notFound("API key", id);
|
|
19529
19807
|
}
|
|
@@ -19539,7 +19817,7 @@ async function keysRoutes(app) {
|
|
|
19539
19817
|
}
|
|
19540
19818
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19541
19819
|
app.db.transaction((tx) => {
|
|
19542
|
-
tx.update(apiKeys).set({ revokedAt: now }).where(
|
|
19820
|
+
tx.update(apiKeys).set({ revokedAt: now }).where(eq21(apiKeys.id, id)).run();
|
|
19543
19821
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
19544
19822
|
actor: "api",
|
|
19545
19823
|
action: "api-key.revoked",
|
|
@@ -19612,7 +19890,7 @@ async function telemetryRoutes(app, opts) {
|
|
|
19612
19890
|
|
|
19613
19891
|
// ../api-routes/src/schedules.ts
|
|
19614
19892
|
import crypto14 from "crypto";
|
|
19615
|
-
import { and as
|
|
19893
|
+
import { and as and17, eq as eq22 } from "drizzle-orm";
|
|
19616
19894
|
function parseKindParam(raw) {
|
|
19617
19895
|
if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
|
|
19618
19896
|
const parsed = schedulableRunKindSchema.safeParse(raw);
|
|
@@ -19639,7 +19917,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
19639
19917
|
if (!sourceId) {
|
|
19640
19918
|
throw validationError('"sourceId" is required when kind is "traffic-sync"');
|
|
19641
19919
|
}
|
|
19642
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
19920
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq22(trafficSources.id, sourceId)).get();
|
|
19643
19921
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
19644
19922
|
throw notFound("Traffic source", sourceId);
|
|
19645
19923
|
}
|
|
@@ -19684,7 +19962,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
19684
19962
|
}
|
|
19685
19963
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19686
19964
|
const enabledBool = enabled !== false;
|
|
19687
|
-
const existing = app.db.select().from(schedules).where(
|
|
19965
|
+
const existing = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
|
|
19688
19966
|
if (existing) {
|
|
19689
19967
|
app.db.update(schedules).set({
|
|
19690
19968
|
cronExpr,
|
|
@@ -19694,7 +19972,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
19694
19972
|
sourceId: sourceId ?? null,
|
|
19695
19973
|
enabled: enabledBool,
|
|
19696
19974
|
updatedAt: now
|
|
19697
|
-
}).where(
|
|
19975
|
+
}).where(eq22(schedules.id, existing.id)).run();
|
|
19698
19976
|
} else {
|
|
19699
19977
|
app.db.insert(schedules).values({
|
|
19700
19978
|
id: crypto14.randomUUID(),
|
|
@@ -19718,13 +19996,13 @@ async function scheduleRoutes(app, opts) {
|
|
|
19718
19996
|
diff: { kind, cronExpr, preset, timezone, providers, sourceId }
|
|
19719
19997
|
});
|
|
19720
19998
|
opts.onScheduleUpdated?.("upsert", project.id, kind);
|
|
19721
|
-
const schedule = app.db.select().from(schedules).where(
|
|
19999
|
+
const schedule = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
|
|
19722
20000
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
19723
20001
|
});
|
|
19724
20002
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
19725
20003
|
const project = resolveProject(app.db, request.params.name);
|
|
19726
20004
|
const kind = parseKindParam(request.query?.kind);
|
|
19727
|
-
const schedule = app.db.select().from(schedules).where(
|
|
20005
|
+
const schedule = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
|
|
19728
20006
|
if (!schedule) {
|
|
19729
20007
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
19730
20008
|
}
|
|
@@ -19733,11 +20011,11 @@ async function scheduleRoutes(app, opts) {
|
|
|
19733
20011
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
19734
20012
|
const project = resolveProject(app.db, request.params.name);
|
|
19735
20013
|
const kind = parseKindParam(request.query?.kind);
|
|
19736
|
-
const schedule = app.db.select().from(schedules).where(
|
|
20014
|
+
const schedule = app.db.select().from(schedules).where(and17(eq22(schedules.projectId, project.id), eq22(schedules.kind, kind))).get();
|
|
19737
20015
|
if (!schedule) {
|
|
19738
20016
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
19739
20017
|
}
|
|
19740
|
-
app.db.delete(schedules).where(
|
|
20018
|
+
app.db.delete(schedules).where(eq22(schedules.id, schedule.id)).run();
|
|
19741
20019
|
writeAuditLog(app.db, {
|
|
19742
20020
|
projectId: project.id,
|
|
19743
20021
|
actor: "api",
|
|
@@ -19770,7 +20048,7 @@ function formatSchedule(row) {
|
|
|
19770
20048
|
|
|
19771
20049
|
// ../api-routes/src/notifications.ts
|
|
19772
20050
|
import crypto15 from "crypto";
|
|
19773
|
-
import { eq as
|
|
20051
|
+
import { eq as eq23 } from "drizzle-orm";
|
|
19774
20052
|
var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
|
|
19775
20053
|
async function notificationRoutes(app, opts = {}) {
|
|
19776
20054
|
const allowLoopback = opts.allowLoopbackWebhooks === true;
|
|
@@ -19810,22 +20088,22 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
19810
20088
|
diff: { channel, ...redactNotificationUrl(url), events }
|
|
19811
20089
|
});
|
|
19812
20090
|
return reply.status(201).send({
|
|
19813
|
-
...formatNotification(app.db.select().from(notifications).where(
|
|
20091
|
+
...formatNotification(app.db.select().from(notifications).where(eq23(notifications.id, id)).get()),
|
|
19814
20092
|
webhookSecret
|
|
19815
20093
|
});
|
|
19816
20094
|
});
|
|
19817
20095
|
app.get("/projects/:name/notifications", async (request, reply) => {
|
|
19818
20096
|
const project = resolveProject(app.db, request.params.name);
|
|
19819
|
-
const rows = app.db.select().from(notifications).where(
|
|
20097
|
+
const rows = app.db.select().from(notifications).where(eq23(notifications.projectId, project.id)).all();
|
|
19820
20098
|
return reply.send(rows.map(formatNotification));
|
|
19821
20099
|
});
|
|
19822
20100
|
app.delete("/projects/:name/notifications/:id", async (request, reply) => {
|
|
19823
20101
|
const project = resolveProject(app.db, request.params.name);
|
|
19824
|
-
const notification = app.db.select().from(notifications).where(
|
|
20102
|
+
const notification = app.db.select().from(notifications).where(eq23(notifications.id, request.params.id)).get();
|
|
19825
20103
|
if (!notification || notification.projectId !== project.id) {
|
|
19826
20104
|
throw notFound("Notification", request.params.id);
|
|
19827
20105
|
}
|
|
19828
|
-
app.db.delete(notifications).where(
|
|
20106
|
+
app.db.delete(notifications).where(eq23(notifications.id, notification.id)).run();
|
|
19829
20107
|
writeAuditLog(app.db, {
|
|
19830
20108
|
projectId: project.id,
|
|
19831
20109
|
actor: "api",
|
|
@@ -19837,7 +20115,7 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
19837
20115
|
});
|
|
19838
20116
|
app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
|
|
19839
20117
|
const project = resolveProject(app.db, request.params.name);
|
|
19840
|
-
const notification = app.db.select().from(notifications).where(
|
|
20118
|
+
const notification = app.db.select().from(notifications).where(eq23(notifications.id, request.params.id)).get();
|
|
19841
20119
|
if (!notification || notification.projectId !== project.id) {
|
|
19842
20120
|
throw notFound("Notification", request.params.id);
|
|
19843
20121
|
}
|
|
@@ -19890,7 +20168,7 @@ function formatNotification(row) {
|
|
|
19890
20168
|
|
|
19891
20169
|
// ../api-routes/src/google.ts
|
|
19892
20170
|
import crypto19 from "crypto";
|
|
19893
|
-
import { eq as
|
|
20171
|
+
import { eq as eq24, and as and18, desc as desc11, sql as sql9, inArray as inArray11 } from "drizzle-orm";
|
|
19894
20172
|
|
|
19895
20173
|
// ../api-routes/src/gbp-summary.ts
|
|
19896
20174
|
function computeMetricTotals(rows) {
|
|
@@ -21730,7 +22008,7 @@ async function googleRoutes(app, opts) {
|
|
|
21730
22008
|
if (!projectId) {
|
|
21731
22009
|
return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
|
|
21732
22010
|
}
|
|
21733
|
-
const project = app.db.select().from(projects).where(
|
|
22011
|
+
const project = app.db.select().from(projects).where(eq24(projects.id, projectId)).get();
|
|
21734
22012
|
if (!project) {
|
|
21735
22013
|
return reply.status(400).send("Project no longer exists. Restart the connect flow.");
|
|
21736
22014
|
}
|
|
@@ -21862,14 +22140,14 @@ async function googleRoutes(app, opts) {
|
|
|
21862
22140
|
if (opts.onGscSyncRequested) {
|
|
21863
22141
|
opts.onGscSyncRequested(runId, project.id, { days, full });
|
|
21864
22142
|
}
|
|
21865
|
-
const run = app.db.select().from(runs).where(
|
|
22143
|
+
const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
|
|
21866
22144
|
return run;
|
|
21867
22145
|
});
|
|
21868
22146
|
app.get("/projects/:name/google/gsc/performance", async (request) => {
|
|
21869
22147
|
const project = resolveProject(app.db, request.params.name);
|
|
21870
22148
|
const { startDate, endDate, query, page, limit, offset } = request.query;
|
|
21871
22149
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
21872
|
-
const conditions = [
|
|
22150
|
+
const conditions = [eq24(gscSearchData.projectId, project.id)];
|
|
21873
22151
|
if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
|
|
21874
22152
|
else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
|
|
21875
22153
|
if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -21877,7 +22155,7 @@ async function googleRoutes(app, opts) {
|
|
|
21877
22155
|
if (page) conditions.push(sql9`${gscSearchData.page} LIKE ${"%" + escapeLikePattern(page) + "%"} ESCAPE '\\'`);
|
|
21878
22156
|
const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
|
|
21879
22157
|
const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
|
|
21880
|
-
const rows = app.db.select().from(gscSearchData).where(
|
|
22158
|
+
const rows = app.db.select().from(gscSearchData).where(and18(...conditions)).orderBy(desc11(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
|
|
21881
22159
|
return rows.map((r) => ({
|
|
21882
22160
|
date: r.date,
|
|
21883
22161
|
query: r.query,
|
|
@@ -21897,7 +22175,7 @@ async function googleRoutes(app, opts) {
|
|
|
21897
22175
|
const windowStart = startDate ?? cutoffDate ?? "";
|
|
21898
22176
|
const windowEnd = endDate ?? "9999-12-31";
|
|
21899
22177
|
const dailyTotals = readGscDailyTotals(app.db, project.id, windowStart, windowEnd);
|
|
21900
|
-
const conditions = [
|
|
22178
|
+
const conditions = [eq24(gscSearchData.projectId, project.id)];
|
|
21901
22179
|
if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
|
|
21902
22180
|
else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
|
|
21903
22181
|
if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -21905,7 +22183,7 @@ async function googleRoutes(app, opts) {
|
|
|
21905
22183
|
date: gscSearchData.date,
|
|
21906
22184
|
clicks: sql9`COALESCE(SUM(${gscSearchData.clicks}), 0)`,
|
|
21907
22185
|
impressions: sql9`COALESCE(SUM(${gscSearchData.impressions}), 0)`
|
|
21908
|
-
}).from(gscSearchData).where(
|
|
22186
|
+
}).from(gscSearchData).where(and18(...conditions)).groupBy(gscSearchData.date).orderBy(gscSearchData.date).all();
|
|
21909
22187
|
const daily = mergeGscDailyTotalsWithFallback(
|
|
21910
22188
|
dailyTotals,
|
|
21911
22189
|
dimensionedRows.map((r) => ({
|
|
@@ -21996,9 +22274,9 @@ async function googleRoutes(app, opts) {
|
|
|
21996
22274
|
app.get("/projects/:name/google/gsc/inspections", async (request) => {
|
|
21997
22275
|
const project = resolveProject(app.db, request.params.name);
|
|
21998
22276
|
const { url, limit } = request.query;
|
|
21999
|
-
const conditions = [
|
|
22000
|
-
if (url) conditions.push(
|
|
22001
|
-
const rows = app.db.select().from(gscUrlInspections).where(
|
|
22277
|
+
const conditions = [eq24(gscUrlInspections.projectId, project.id)];
|
|
22278
|
+
if (url) conditions.push(eq24(gscUrlInspections.url, url));
|
|
22279
|
+
const rows = app.db.select().from(gscUrlInspections).where(and18(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
|
|
22002
22280
|
return rows.map((r) => ({
|
|
22003
22281
|
id: r.id,
|
|
22004
22282
|
url: r.url,
|
|
@@ -22017,7 +22295,7 @@ async function googleRoutes(app, opts) {
|
|
|
22017
22295
|
});
|
|
22018
22296
|
app.get("/projects/:name/google/gsc/deindexed", async (request) => {
|
|
22019
22297
|
const project = resolveProject(app.db, request.params.name);
|
|
22020
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
22298
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq24(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
|
|
22021
22299
|
const byUrl = /* @__PURE__ */ new Map();
|
|
22022
22300
|
for (const row of allInspections) {
|
|
22023
22301
|
const existing = byUrl.get(row.url);
|
|
@@ -22045,7 +22323,7 @@ async function googleRoutes(app, opts) {
|
|
|
22045
22323
|
});
|
|
22046
22324
|
app.get("/projects/:name/google/gsc/coverage", async (request) => {
|
|
22047
22325
|
const project = resolveProject(app.db, request.params.name);
|
|
22048
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
22326
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq24(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
|
|
22049
22327
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
22050
22328
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
22051
22329
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -22094,7 +22372,7 @@ async function googleRoutes(app, opts) {
|
|
|
22094
22372
|
const total = latestByUrl.size;
|
|
22095
22373
|
const indexed = indexedUrls.length;
|
|
22096
22374
|
const notIndexed = notIndexedUrls.length;
|
|
22097
|
-
const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(
|
|
22375
|
+
const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq24(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
|
|
22098
22376
|
const lastSyncedAt = latestSnapshot?.createdAt ?? null;
|
|
22099
22377
|
const formatRow = (r) => ({
|
|
22100
22378
|
id: r.id,
|
|
@@ -22145,7 +22423,7 @@ async function googleRoutes(app, opts) {
|
|
|
22145
22423
|
const project = resolveProject(app.db, request.params.name);
|
|
22146
22424
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
22147
22425
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
22148
|
-
const rows = app.db.select().from(gscCoverageSnapshots).where(
|
|
22426
|
+
const rows = app.db.select().from(gscCoverageSnapshots).where(eq24(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
|
|
22149
22427
|
return rows.map((r) => ({
|
|
22150
22428
|
date: r.date,
|
|
22151
22429
|
indexed: r.indexed,
|
|
@@ -22214,7 +22492,7 @@ async function googleRoutes(app, opts) {
|
|
|
22214
22492
|
if (opts.onInspectSitemapRequested) {
|
|
22215
22493
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
|
|
22216
22494
|
}
|
|
22217
|
-
const run = app.db.select().from(runs).where(
|
|
22495
|
+
const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
|
|
22218
22496
|
return { sitemaps, primarySitemapUrl: sitemapUrl, run };
|
|
22219
22497
|
});
|
|
22220
22498
|
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
|
|
@@ -22241,7 +22519,7 @@ async function googleRoutes(app, opts) {
|
|
|
22241
22519
|
if (opts.onInspectSitemapRequested) {
|
|
22242
22520
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
|
|
22243
22521
|
}
|
|
22244
|
-
const run = app.db.select().from(runs).where(
|
|
22522
|
+
const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
|
|
22245
22523
|
return run;
|
|
22246
22524
|
});
|
|
22247
22525
|
app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
|
|
@@ -22288,7 +22566,7 @@ async function googleRoutes(app, opts) {
|
|
|
22288
22566
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
22289
22567
|
let urlsToNotify = request.body?.urls ?? [];
|
|
22290
22568
|
if (request.body?.allUnindexed) {
|
|
22291
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
22569
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq24(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
|
|
22292
22570
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
22293
22571
|
for (const row of allInspections) {
|
|
22294
22572
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -22406,7 +22684,7 @@ async function googleRoutes(app, opts) {
|
|
|
22406
22684
|
};
|
|
22407
22685
|
}
|
|
22408
22686
|
function listSelectionResponse(projectId) {
|
|
22409
|
-
const rows = app.db.select().from(gbpLocations).where(
|
|
22687
|
+
const rows = app.db.select().from(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).all();
|
|
22410
22688
|
const dtos = rows.map(rowToDto2);
|
|
22411
22689
|
return {
|
|
22412
22690
|
locations: dtos,
|
|
@@ -22415,15 +22693,15 @@ async function googleRoutes(app, opts) {
|
|
|
22415
22693
|
};
|
|
22416
22694
|
}
|
|
22417
22695
|
function clearGbpProjectData(tx, projectId) {
|
|
22418
|
-
tx.delete(gbpDailyMetrics).where(
|
|
22419
|
-
tx.delete(gbpKeywordImpressions).where(
|
|
22420
|
-
tx.delete(gbpKeywordMonthly).where(
|
|
22421
|
-
tx.delete(gbpPlaceActions).where(
|
|
22422
|
-
tx.delete(gbpLodgingSnapshots).where(
|
|
22423
|
-
tx.delete(gbpLocations).where(
|
|
22696
|
+
tx.delete(gbpDailyMetrics).where(eq24(gbpDailyMetrics.projectId, projectId)).run();
|
|
22697
|
+
tx.delete(gbpKeywordImpressions).where(eq24(gbpKeywordImpressions.projectId, projectId)).run();
|
|
22698
|
+
tx.delete(gbpKeywordMonthly).where(eq24(gbpKeywordMonthly.projectId, projectId)).run();
|
|
22699
|
+
tx.delete(gbpPlaceActions).where(eq24(gbpPlaceActions.projectId, projectId)).run();
|
|
22700
|
+
tx.delete(gbpLodgingSnapshots).where(eq24(gbpLodgingSnapshots.projectId, projectId)).run();
|
|
22701
|
+
tx.delete(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).run();
|
|
22424
22702
|
}
|
|
22425
22703
|
function currentProjectAccount(projectId) {
|
|
22426
|
-
const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(
|
|
22704
|
+
const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).limit(1).get();
|
|
22427
22705
|
return row?.accountName ?? null;
|
|
22428
22706
|
}
|
|
22429
22707
|
app.post("/projects/:name/gbp/locations/discover", async (request) => {
|
|
@@ -22493,7 +22771,7 @@ async function googleRoutes(app, opts) {
|
|
|
22493
22771
|
app.db.transaction((tx) => {
|
|
22494
22772
|
if (switching) clearGbpProjectData(tx, project.id);
|
|
22495
22773
|
for (const remote of remoteLocations) {
|
|
22496
|
-
const existing = tx.select().from(gbpLocations).where(
|
|
22774
|
+
const existing = tx.select().from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), eq24(gbpLocations.locationName, remote.name))).get();
|
|
22497
22775
|
const profile = buildLocationProfileFields(remote);
|
|
22498
22776
|
if (existing) {
|
|
22499
22777
|
tx.update(gbpLocations).set({
|
|
@@ -22506,7 +22784,7 @@ async function googleRoutes(app, opts) {
|
|
|
22506
22784
|
mapsUri: remote.metadata?.mapsUri ?? null,
|
|
22507
22785
|
...profile,
|
|
22508
22786
|
updatedAt: now
|
|
22509
|
-
}).where(
|
|
22787
|
+
}).where(eq24(gbpLocations.id, existing.id)).run();
|
|
22510
22788
|
} else {
|
|
22511
22789
|
tx.insert(gbpLocations).values({
|
|
22512
22790
|
id: crypto19.randomUUID(),
|
|
@@ -22590,11 +22868,11 @@ async function googleRoutes(app, opts) {
|
|
|
22590
22868
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
|
|
22591
22869
|
}
|
|
22592
22870
|
const { selected } = parsed.data;
|
|
22593
|
-
const existing = app.db.select().from(gbpLocations).where(
|
|
22871
|
+
const existing = app.db.select().from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), eq24(gbpLocations.locationName, locationName))).get();
|
|
22594
22872
|
if (!existing) throw notFound("GBP location", locationName);
|
|
22595
22873
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
22596
22874
|
app.db.transaction((tx) => {
|
|
22597
|
-
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(
|
|
22875
|
+
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq24(gbpLocations.id, existing.id)).run();
|
|
22598
22876
|
writeAuditLog(tx, {
|
|
22599
22877
|
projectId: project.id,
|
|
22600
22878
|
actor: "api",
|
|
@@ -22603,7 +22881,7 @@ async function googleRoutes(app, opts) {
|
|
|
22603
22881
|
entityId: locationName
|
|
22604
22882
|
});
|
|
22605
22883
|
});
|
|
22606
|
-
const refreshed = app.db.select().from(gbpLocations).where(
|
|
22884
|
+
const refreshed = app.db.select().from(gbpLocations).where(eq24(gbpLocations.id, existing.id)).get();
|
|
22607
22885
|
return rowToDto2(refreshed);
|
|
22608
22886
|
});
|
|
22609
22887
|
app.delete("/projects/:name/gbp/connection", async (request, reply) => {
|
|
@@ -22647,10 +22925,10 @@ async function googleRoutes(app, opts) {
|
|
|
22647
22925
|
});
|
|
22648
22926
|
app.get("/projects/:name/gbp/metrics", async (request) => {
|
|
22649
22927
|
const project = resolveProject(app.db, request.params.name);
|
|
22650
|
-
const conditions = [
|
|
22651
|
-
if (request.query.locationName) conditions.push(
|
|
22652
|
-
if (request.query.metric) conditions.push(
|
|
22653
|
-
const rows = app.db.select().from(gbpDailyMetrics).where(
|
|
22928
|
+
const conditions = [eq24(gbpDailyMetrics.projectId, project.id)];
|
|
22929
|
+
if (request.query.locationName) conditions.push(eq24(gbpDailyMetrics.locationName, request.query.locationName));
|
|
22930
|
+
if (request.query.metric) conditions.push(eq24(gbpDailyMetrics.metric, request.query.metric));
|
|
22931
|
+
const rows = app.db.select().from(gbpDailyMetrics).where(and18(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
|
|
22654
22932
|
return {
|
|
22655
22933
|
metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
|
|
22656
22934
|
total: rows.length
|
|
@@ -22658,9 +22936,9 @@ async function googleRoutes(app, opts) {
|
|
|
22658
22936
|
});
|
|
22659
22937
|
app.get("/projects/:name/gbp/keywords", async (request) => {
|
|
22660
22938
|
const project = resolveProject(app.db, request.params.name);
|
|
22661
|
-
const conditions = [
|
|
22662
|
-
if (request.query.locationName) conditions.push(
|
|
22663
|
-
const rows = app.db.select().from(gbpKeywordImpressions).where(
|
|
22939
|
+
const conditions = [eq24(gbpKeywordImpressions.projectId, project.id)];
|
|
22940
|
+
if (request.query.locationName) conditions.push(eq24(gbpKeywordImpressions.locationName, request.query.locationName));
|
|
22941
|
+
const rows = app.db.select().from(gbpKeywordImpressions).where(and18(...conditions)).all();
|
|
22664
22942
|
rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
|
|
22665
22943
|
const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
|
|
22666
22944
|
return {
|
|
@@ -22678,9 +22956,9 @@ async function googleRoutes(app, opts) {
|
|
|
22678
22956
|
});
|
|
22679
22957
|
app.get("/projects/:name/gbp/place-actions", async (request) => {
|
|
22680
22958
|
const project = resolveProject(app.db, request.params.name);
|
|
22681
|
-
const conditions = [
|
|
22682
|
-
if (request.query.locationName) conditions.push(
|
|
22683
|
-
const rows = app.db.select().from(gbpPlaceActions).where(
|
|
22959
|
+
const conditions = [eq24(gbpPlaceActions.projectId, project.id)];
|
|
22960
|
+
if (request.query.locationName) conditions.push(eq24(gbpPlaceActions.locationName, request.query.locationName));
|
|
22961
|
+
const rows = app.db.select().from(gbpPlaceActions).where(and18(...conditions)).all();
|
|
22684
22962
|
return {
|
|
22685
22963
|
placeActions: rows.map((r) => ({
|
|
22686
22964
|
locationName: r.locationName,
|
|
@@ -22695,9 +22973,9 @@ async function googleRoutes(app, opts) {
|
|
|
22695
22973
|
});
|
|
22696
22974
|
app.get("/projects/:name/gbp/lodging", async (request) => {
|
|
22697
22975
|
const project = resolveProject(app.db, request.params.name);
|
|
22698
|
-
const conditions = [
|
|
22699
|
-
if (request.query.locationName) conditions.push(
|
|
22700
|
-
const rows = app.db.select().from(gbpLodgingSnapshots).where(
|
|
22976
|
+
const conditions = [eq24(gbpLodgingSnapshots.projectId, project.id)];
|
|
22977
|
+
if (request.query.locationName) conditions.push(eq24(gbpLodgingSnapshots.locationName, request.query.locationName));
|
|
22978
|
+
const rows = app.db.select().from(gbpLodgingSnapshots).where(and18(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
|
|
22701
22979
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
22702
22980
|
for (const row of rows) {
|
|
22703
22981
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -22712,9 +22990,9 @@ async function googleRoutes(app, opts) {
|
|
|
22712
22990
|
});
|
|
22713
22991
|
app.get("/projects/:name/gbp/attributes", async (request) => {
|
|
22714
22992
|
const project = resolveProject(app.db, request.params.name);
|
|
22715
|
-
const conditions = [
|
|
22716
|
-
if (request.query.locationName) conditions.push(
|
|
22717
|
-
const rows = app.db.select().from(gbpAttributesSnapshots).where(
|
|
22993
|
+
const conditions = [eq24(gbpAttributesSnapshots.projectId, project.id)];
|
|
22994
|
+
if (request.query.locationName) conditions.push(eq24(gbpAttributesSnapshots.locationName, request.query.locationName));
|
|
22995
|
+
const rows = app.db.select().from(gbpAttributesSnapshots).where(and18(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
|
|
22718
22996
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
22719
22997
|
for (const row of rows) {
|
|
22720
22998
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -22732,9 +23010,9 @@ async function googleRoutes(app, opts) {
|
|
|
22732
23010
|
});
|
|
22733
23011
|
app.get("/projects/:name/gbp/places", async (request) => {
|
|
22734
23012
|
const project = resolveProject(app.db, request.params.name);
|
|
22735
|
-
const conditions = [
|
|
22736
|
-
if (request.query.locationName) conditions.push(
|
|
22737
|
-
const rows = app.db.select().from(gbpPlaceDetails).where(
|
|
23013
|
+
const conditions = [eq24(gbpPlaceDetails.projectId, project.id)];
|
|
23014
|
+
if (request.query.locationName) conditions.push(eq24(gbpPlaceDetails.locationName, request.query.locationName));
|
|
23015
|
+
const rows = app.db.select().from(gbpPlaceDetails).where(and18(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
|
|
22738
23016
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
22739
23017
|
for (const row of rows) {
|
|
22740
23018
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -22753,7 +23031,7 @@ async function googleRoutes(app, opts) {
|
|
|
22753
23031
|
app.get("/projects/:name/gbp/summary", async (request) => {
|
|
22754
23032
|
const project = resolveProject(app.db, request.params.name);
|
|
22755
23033
|
const locationName = request.query.locationName ?? null;
|
|
22756
|
-
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(
|
|
23034
|
+
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), eq24(gbpLocations.selected, true))).all().map((r) => r.n);
|
|
22757
23035
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
22758
23036
|
if (locationNames.length === 0) {
|
|
22759
23037
|
return buildGbpSummary({
|
|
@@ -22767,10 +23045,10 @@ async function googleRoutes(app, opts) {
|
|
|
22767
23045
|
locationProfiles: []
|
|
22768
23046
|
});
|
|
22769
23047
|
}
|
|
22770
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(
|
|
22771
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(
|
|
22772
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(
|
|
22773
|
-
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(
|
|
23048
|
+
const metricRows = app.db.select().from(gbpDailyMetrics).where(and18(eq24(gbpDailyMetrics.projectId, project.id), inArray11(gbpDailyMetrics.locationName, locationNames))).all();
|
|
23049
|
+
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and18(eq24(gbpKeywordImpressions.projectId, project.id), inArray11(gbpKeywordImpressions.locationName, locationNames))).all();
|
|
23050
|
+
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and18(eq24(gbpPlaceActions.projectId, project.id), inArray11(gbpPlaceActions.locationName, locationNames))).all();
|
|
23051
|
+
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and18(eq24(gbpLodgingSnapshots.projectId, project.id), inArray11(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
|
|
22774
23052
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
22775
23053
|
for (const row of lodgingRows) {
|
|
22776
23054
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -22784,7 +23062,7 @@ async function googleRoutes(app, opts) {
|
|
|
22784
23062
|
regularHours: gbpLocations.regularHours,
|
|
22785
23063
|
primaryPhone: gbpLocations.primaryPhone,
|
|
22786
23064
|
openStatus: gbpLocations.openStatus
|
|
22787
|
-
}).from(gbpLocations).where(
|
|
23065
|
+
}).from(gbpLocations).where(and18(eq24(gbpLocations.projectId, project.id), inArray11(gbpLocations.locationName, locationNames))).all();
|
|
22788
23066
|
return buildGbpSummary({
|
|
22789
23067
|
locationName,
|
|
22790
23068
|
locationCount: locationNames.length,
|
|
@@ -22800,7 +23078,7 @@ async function googleRoutes(app, opts) {
|
|
|
22800
23078
|
|
|
22801
23079
|
// ../api-routes/src/ads.ts
|
|
22802
23080
|
import crypto20 from "crypto";
|
|
22803
|
-
import { eq as
|
|
23081
|
+
import { eq as eq25, and as and19, asc as asc4, gte as gte4, lte as lte3, inArray as inArray12 } from "drizzle-orm";
|
|
22804
23082
|
function statusDto(row) {
|
|
22805
23083
|
if (!row) return { connected: false };
|
|
22806
23084
|
return {
|
|
@@ -22850,7 +23128,7 @@ async function adsRoutes(app, opts) {
|
|
|
22850
23128
|
createdAt: existingCfg?.createdAt ?? now,
|
|
22851
23129
|
updatedAt: now
|
|
22852
23130
|
});
|
|
22853
|
-
const existingRow = app.db.select().from(adsConnections).where(
|
|
23131
|
+
const existingRow = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
22854
23132
|
app.db.transaction((tx) => {
|
|
22855
23133
|
if (existingRow) {
|
|
22856
23134
|
tx.update(adsConnections).set({
|
|
@@ -22860,7 +23138,7 @@ async function adsRoutes(app, opts) {
|
|
|
22860
23138
|
timezone: account.timezone,
|
|
22861
23139
|
status: account.status,
|
|
22862
23140
|
updatedAt: now
|
|
22863
|
-
}).where(
|
|
23141
|
+
}).where(eq25(adsConnections.id, existingRow.id)).run();
|
|
22864
23142
|
} else {
|
|
22865
23143
|
tx.insert(adsConnections).values({
|
|
22866
23144
|
id: crypto20.randomUUID(),
|
|
@@ -22882,16 +23160,16 @@ async function adsRoutes(app, opts) {
|
|
|
22882
23160
|
entityId: account.id
|
|
22883
23161
|
}));
|
|
22884
23162
|
});
|
|
22885
|
-
const row = app.db.select().from(adsConnections).where(
|
|
23163
|
+
const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
22886
23164
|
return statusDto(row);
|
|
22887
23165
|
}
|
|
22888
23166
|
);
|
|
22889
23167
|
app.delete("/projects/:name/ads/connection", async (request) => {
|
|
22890
23168
|
const project = resolveProject(app.db, request.params.name);
|
|
22891
|
-
const row = app.db.select().from(adsConnections).where(
|
|
23169
|
+
const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
22892
23170
|
if (row) {
|
|
22893
23171
|
app.db.transaction((tx) => {
|
|
22894
|
-
tx.delete(adsConnections).where(
|
|
23172
|
+
tx.delete(adsConnections).where(eq25(adsConnections.id, row.id)).run();
|
|
22895
23173
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
22896
23174
|
projectId: project.id,
|
|
22897
23175
|
actor: "api",
|
|
@@ -22907,18 +23185,18 @@ async function adsRoutes(app, opts) {
|
|
|
22907
23185
|
});
|
|
22908
23186
|
app.get("/projects/:name/ads/status", async (request) => {
|
|
22909
23187
|
const project = resolveProject(app.db, request.params.name);
|
|
22910
|
-
const row = app.db.select().from(adsConnections).where(
|
|
23188
|
+
const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
22911
23189
|
return statusDto(row);
|
|
22912
23190
|
});
|
|
22913
23191
|
app.post("/projects/:name/ads/sync", async (request) => {
|
|
22914
23192
|
const project = resolveProject(app.db, request.params.name);
|
|
22915
|
-
const row = app.db.select().from(adsConnections).where(
|
|
23193
|
+
const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
22916
23194
|
if (!row) {
|
|
22917
23195
|
throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
|
|
22918
23196
|
}
|
|
22919
|
-
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
22920
|
-
|
|
22921
|
-
|
|
23197
|
+
const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and19(
|
|
23198
|
+
eq25(runs.projectId, project.id),
|
|
23199
|
+
eq25(runs.kind, RunKinds["ads-sync"]),
|
|
22922
23200
|
inArray12(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
22923
23201
|
)).get();
|
|
22924
23202
|
if (inFlight) {
|
|
@@ -22940,9 +23218,9 @@ async function adsRoutes(app, opts) {
|
|
|
22940
23218
|
});
|
|
22941
23219
|
app.get("/projects/:name/ads/campaigns", async (request) => {
|
|
22942
23220
|
const project = resolveProject(app.db, request.params.name);
|
|
22943
|
-
const campaignRows = app.db.select().from(adsCampaigns).where(
|
|
22944
|
-
const groupRows = app.db.select().from(adsAdGroups).where(
|
|
22945
|
-
const adRows = app.db.select().from(adsAds).where(
|
|
23221
|
+
const campaignRows = app.db.select().from(adsCampaigns).where(eq25(adsCampaigns.projectId, project.id)).all();
|
|
23222
|
+
const groupRows = app.db.select().from(adsAdGroups).where(eq25(adsAdGroups.projectId, project.id)).all();
|
|
23223
|
+
const adRows = app.db.select().from(adsAds).where(eq25(adsAds.projectId, project.id)).all();
|
|
22946
23224
|
const adsByGroup = /* @__PURE__ */ new Map();
|
|
22947
23225
|
for (const ad of adRows) {
|
|
22948
23226
|
const dto = {
|
|
@@ -22996,12 +23274,12 @@ async function adsRoutes(app, opts) {
|
|
|
22996
23274
|
}
|
|
22997
23275
|
parsedLevel = result.data;
|
|
22998
23276
|
}
|
|
22999
|
-
const conditions = [
|
|
23000
|
-
if (parsedLevel) conditions.push(
|
|
23001
|
-
if (entityId) conditions.push(
|
|
23002
|
-
if (from) conditions.push(
|
|
23003
|
-
if (to) conditions.push(
|
|
23004
|
-
const rows = app.db.select().from(adsInsightsDaily).where(
|
|
23277
|
+
const conditions = [eq25(adsInsightsDaily.projectId, project.id)];
|
|
23278
|
+
if (parsedLevel) conditions.push(eq25(adsInsightsDaily.level, parsedLevel));
|
|
23279
|
+
if (entityId) conditions.push(eq25(adsInsightsDaily.entityId, entityId));
|
|
23280
|
+
if (from) conditions.push(gte4(adsInsightsDaily.date, from));
|
|
23281
|
+
if (to) conditions.push(lte3(adsInsightsDaily.date, to));
|
|
23282
|
+
const rows = app.db.select().from(adsInsightsDaily).where(and19(...conditions)).orderBy(asc4(adsInsightsDaily.date)).all();
|
|
23005
23283
|
const dtoRows = rows.map((row) => ({
|
|
23006
23284
|
level: row.level,
|
|
23007
23285
|
entityId: row.entityId,
|
|
@@ -23013,19 +23291,19 @@ async function adsRoutes(app, opts) {
|
|
|
23013
23291
|
ctr: adsCtr(row.clicks, row.impressions),
|
|
23014
23292
|
cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
|
|
23015
23293
|
}));
|
|
23016
|
-
const conn = app.db.select().from(adsConnections).where(
|
|
23294
|
+
const conn = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
23017
23295
|
const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
|
|
23018
23296
|
return response;
|
|
23019
23297
|
});
|
|
23020
23298
|
app.get("/projects/:name/ads/summary", async (request) => {
|
|
23021
23299
|
const project = resolveProject(app.db, request.params.name);
|
|
23022
|
-
const row = app.db.select().from(adsConnections).where(
|
|
23023
|
-
const campaignCount = app.db.select().from(adsCampaigns).where(
|
|
23024
|
-
const adGroupCount = app.db.select().from(adsAdGroups).where(
|
|
23025
|
-
const adCount = app.db.select().from(adsAds).where(
|
|
23026
|
-
const campaignInsights = app.db.select().from(adsInsightsDaily).where(
|
|
23027
|
-
|
|
23028
|
-
|
|
23300
|
+
const row = app.db.select().from(adsConnections).where(eq25(adsConnections.projectId, project.id)).get();
|
|
23301
|
+
const campaignCount = app.db.select().from(adsCampaigns).where(eq25(adsCampaigns.projectId, project.id)).all().length;
|
|
23302
|
+
const adGroupCount = app.db.select().from(adsAdGroups).where(eq25(adsAdGroups.projectId, project.id)).all().length;
|
|
23303
|
+
const adCount = app.db.select().from(adsAds).where(eq25(adsAds.projectId, project.id)).all().length;
|
|
23304
|
+
const campaignInsights = app.db.select().from(adsInsightsDaily).where(and19(
|
|
23305
|
+
eq25(adsInsightsDaily.projectId, project.id),
|
|
23306
|
+
eq25(adsInsightsDaily.level, "campaign")
|
|
23029
23307
|
)).all();
|
|
23030
23308
|
let impressions = 0;
|
|
23031
23309
|
let clicks = 0;
|
|
@@ -23065,7 +23343,7 @@ async function adsRoutes(app, opts) {
|
|
|
23065
23343
|
|
|
23066
23344
|
// ../api-routes/src/bing.ts
|
|
23067
23345
|
import crypto21 from "crypto";
|
|
23068
|
-
import { eq as
|
|
23346
|
+
import { eq as eq26, and as and20, desc as desc12 } from "drizzle-orm";
|
|
23069
23347
|
|
|
23070
23348
|
// ../integration-bing/src/constants.ts
|
|
23071
23349
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -23439,7 +23717,7 @@ async function bingRoutes(app, opts) {
|
|
|
23439
23717
|
const store = requireConnectionStore();
|
|
23440
23718
|
const project = resolveProject(app.db, request.params.name);
|
|
23441
23719
|
requireConnection(store, project.canonicalDomain);
|
|
23442
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
23720
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq26(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
|
|
23443
23721
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
23444
23722
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
23445
23723
|
for (const row of allInspections) {
|
|
@@ -23528,7 +23806,7 @@ async function bingRoutes(app, opts) {
|
|
|
23528
23806
|
const project = resolveProject(app.db, request.params.name);
|
|
23529
23807
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
23530
23808
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
23531
|
-
const rows = app.db.select().from(bingCoverageSnapshots).where(
|
|
23809
|
+
const rows = app.db.select().from(bingCoverageSnapshots).where(eq26(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
|
|
23532
23810
|
return rows.map((r) => ({
|
|
23533
23811
|
date: r.date,
|
|
23534
23812
|
indexed: r.indexed,
|
|
@@ -23540,7 +23818,7 @@ async function bingRoutes(app, opts) {
|
|
|
23540
23818
|
requireConnectionStore();
|
|
23541
23819
|
const project = resolveProject(app.db, request.params.name);
|
|
23542
23820
|
const { url, limit } = request.query;
|
|
23543
|
-
const whereClause = url ?
|
|
23821
|
+
const whereClause = url ? and20(eq26(bingUrlInspections.projectId, project.id), eq26(bingUrlInspections.url, url)) : eq26(bingUrlInspections.projectId, project.id);
|
|
23544
23822
|
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc12(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
23545
23823
|
return filtered.map((r) => ({
|
|
23546
23824
|
id: r.id,
|
|
@@ -23630,7 +23908,7 @@ async function bingRoutes(app, opts) {
|
|
|
23630
23908
|
anchorCount: result.AnchorCount ?? null,
|
|
23631
23909
|
discoveryDate
|
|
23632
23910
|
}).run();
|
|
23633
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
23911
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq26(runs.id, runId)).run();
|
|
23634
23912
|
return {
|
|
23635
23913
|
id,
|
|
23636
23914
|
url,
|
|
@@ -23646,7 +23924,7 @@ async function bingRoutes(app, opts) {
|
|
|
23646
23924
|
} catch (e) {
|
|
23647
23925
|
const msg = e instanceof Error ? e.message : String(e);
|
|
23648
23926
|
bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
|
|
23649
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
23927
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(runs.id, runId)).run();
|
|
23650
23928
|
throw e;
|
|
23651
23929
|
}
|
|
23652
23930
|
});
|
|
@@ -23673,7 +23951,7 @@ async function bingRoutes(app, opts) {
|
|
|
23673
23951
|
} else {
|
|
23674
23952
|
bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
|
|
23675
23953
|
}
|
|
23676
|
-
const run = app.db.select().from(runs).where(
|
|
23954
|
+
const run = app.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
23677
23955
|
return run;
|
|
23678
23956
|
});
|
|
23679
23957
|
app.post("/projects/:name/bing/request-indexing", async (request) => {
|
|
@@ -23685,7 +23963,7 @@ async function bingRoutes(app, opts) {
|
|
|
23685
23963
|
}
|
|
23686
23964
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
23687
23965
|
if (request.body?.allUnindexed) {
|
|
23688
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
23966
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq26(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
|
|
23689
23967
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
23690
23968
|
for (const row of allInspections) {
|
|
23691
23969
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -23772,14 +24050,14 @@ async function bingRoutes(app, opts) {
|
|
|
23772
24050
|
import fs from "fs";
|
|
23773
24051
|
import path from "path";
|
|
23774
24052
|
import os from "os";
|
|
23775
|
-
import { eq as
|
|
24053
|
+
import { eq as eq27, and as and21 } from "drizzle-orm";
|
|
23776
24054
|
function getScreenshotDir() {
|
|
23777
24055
|
return path.join(os.homedir(), ".canonry", "screenshots");
|
|
23778
24056
|
}
|
|
23779
24057
|
async function cdpRoutes(app, opts) {
|
|
23780
24058
|
app.get("/screenshots/:snapshotId", async (request, reply) => {
|
|
23781
24059
|
const { snapshotId } = request.params;
|
|
23782
|
-
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs,
|
|
24060
|
+
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs, eq27(querySnapshots.runId, runs.id)).where(eq27(querySnapshots.id, snapshotId)).get();
|
|
23783
24061
|
if (!snapshot?.screenshotPath) {
|
|
23784
24062
|
const err = notFound("Screenshot", snapshotId);
|
|
23785
24063
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -23847,7 +24125,7 @@ async function cdpRoutes(app, opts) {
|
|
|
23847
24125
|
async (request, reply) => {
|
|
23848
24126
|
const project = resolveProject(app.db, request.params.name);
|
|
23849
24127
|
const { runId } = request.params;
|
|
23850
|
-
const run = app.db.select().from(runs).where(
|
|
24128
|
+
const run = app.db.select().from(runs).where(and21(eq27(runs.id, runId), eq27(runs.projectId, project.id))).get();
|
|
23851
24129
|
if (!run) {
|
|
23852
24130
|
const err = notFound("Run", runId);
|
|
23853
24131
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -23860,8 +24138,8 @@ async function cdpRoutes(app, opts) {
|
|
|
23860
24138
|
citedDomains: querySnapshots.citedDomains,
|
|
23861
24139
|
screenshotPath: querySnapshots.screenshotPath,
|
|
23862
24140
|
rawResponse: querySnapshots.rawResponse
|
|
23863
|
-
}).from(querySnapshots).where(
|
|
23864
|
-
const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
24141
|
+
}).from(querySnapshots).where(eq27(querySnapshots.runId, runId)).all());
|
|
24142
|
+
const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq27(queries.projectId, project.id)).all();
|
|
23865
24143
|
const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
|
|
23866
24144
|
const byQuery = /* @__PURE__ */ new Map();
|
|
23867
24145
|
for (const snap of snapshots) {
|
|
@@ -23944,7 +24222,7 @@ async function cdpRoutes(app, opts) {
|
|
|
23944
24222
|
|
|
23945
24223
|
// ../api-routes/src/ga.ts
|
|
23946
24224
|
import crypto22 from "crypto";
|
|
23947
|
-
import { eq as
|
|
24225
|
+
import { eq as eq28, desc as desc13, and as and22, sql as sql10 } from "drizzle-orm";
|
|
23948
24226
|
function gaLog(level, action, ctx) {
|
|
23949
24227
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
23950
24228
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -24221,10 +24499,10 @@ async function ga4Routes(app, opts) {
|
|
|
24221
24499
|
if (!saConn && !oauthConn) {
|
|
24222
24500
|
throw notFound("GA4 connection", project.name);
|
|
24223
24501
|
}
|
|
24224
|
-
app.db.delete(gaTrafficSnapshots).where(
|
|
24225
|
-
app.db.delete(gaTrafficSummaries).where(
|
|
24226
|
-
app.db.delete(gaAiReferrals).where(
|
|
24227
|
-
app.db.delete(gaSocialReferrals).where(
|
|
24502
|
+
app.db.delete(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, project.id)).run();
|
|
24503
|
+
app.db.delete(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).run();
|
|
24504
|
+
app.db.delete(gaAiReferrals).where(eq28(gaAiReferrals.projectId, project.id)).run();
|
|
24505
|
+
app.db.delete(gaSocialReferrals).where(eq28(gaSocialReferrals.projectId, project.id)).run();
|
|
24228
24506
|
const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
|
|
24229
24507
|
opts.ga4CredentialStore?.deleteConnection(project.name);
|
|
24230
24508
|
opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
|
|
@@ -24245,7 +24523,7 @@ async function ga4Routes(app, opts) {
|
|
|
24245
24523
|
if (!connected) {
|
|
24246
24524
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
24247
24525
|
}
|
|
24248
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
24526
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
24249
24527
|
return {
|
|
24250
24528
|
connected: true,
|
|
24251
24529
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -24309,8 +24587,8 @@ async function ga4Routes(app, opts) {
|
|
|
24309
24587
|
app.db.transaction((tx) => {
|
|
24310
24588
|
if (syncTraffic) {
|
|
24311
24589
|
tx.delete(gaTrafficSnapshots).where(
|
|
24312
|
-
|
|
24313
|
-
|
|
24590
|
+
and22(
|
|
24591
|
+
eq28(gaTrafficSnapshots.projectId, project.id),
|
|
24314
24592
|
sql10`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
24315
24593
|
sql10`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
24316
24594
|
)
|
|
@@ -24333,8 +24611,8 @@ async function ga4Routes(app, opts) {
|
|
|
24333
24611
|
}
|
|
24334
24612
|
if (syncAi) {
|
|
24335
24613
|
tx.delete(gaAiReferrals).where(
|
|
24336
|
-
|
|
24337
|
-
|
|
24614
|
+
and22(
|
|
24615
|
+
eq28(gaAiReferrals.projectId, project.id),
|
|
24338
24616
|
sql10`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
24339
24617
|
sql10`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
24340
24618
|
)
|
|
@@ -24365,8 +24643,8 @@ async function ga4Routes(app, opts) {
|
|
|
24365
24643
|
}
|
|
24366
24644
|
if (syncSocial) {
|
|
24367
24645
|
tx.delete(gaSocialReferrals).where(
|
|
24368
|
-
|
|
24369
|
-
|
|
24646
|
+
and22(
|
|
24647
|
+
eq28(gaSocialReferrals.projectId, project.id),
|
|
24370
24648
|
sql10`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
24371
24649
|
sql10`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
24372
24650
|
)
|
|
@@ -24387,7 +24665,7 @@ async function ga4Routes(app, opts) {
|
|
|
24387
24665
|
}
|
|
24388
24666
|
}
|
|
24389
24667
|
if (syncSummary) {
|
|
24390
|
-
tx.delete(gaTrafficSummaries).where(
|
|
24668
|
+
tx.delete(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).run();
|
|
24391
24669
|
tx.insert(gaTrafficSummaries).values({
|
|
24392
24670
|
id: crypto22.randomUUID(),
|
|
24393
24671
|
projectId: project.id,
|
|
@@ -24399,7 +24677,7 @@ async function ga4Routes(app, opts) {
|
|
|
24399
24677
|
syncedAt: now,
|
|
24400
24678
|
syncRunId: runId
|
|
24401
24679
|
}).run();
|
|
24402
|
-
tx.delete(gaTrafficWindowSummaries).where(
|
|
24680
|
+
tx.delete(gaTrafficWindowSummaries).where(eq28(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
24403
24681
|
for (const ws of windowSummaries) {
|
|
24404
24682
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
24405
24683
|
id: crypto22.randomUUID(),
|
|
@@ -24417,7 +24695,7 @@ async function ga4Routes(app, opts) {
|
|
|
24417
24695
|
}
|
|
24418
24696
|
}
|
|
24419
24697
|
});
|
|
24420
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
24698
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq28(runs.id, runId)).run();
|
|
24421
24699
|
const syncedComponents = only ? [
|
|
24422
24700
|
...syncTraffic ? ["traffic"] : [],
|
|
24423
24701
|
...syncSummary ? ["summary"] : [],
|
|
@@ -24446,7 +24724,7 @@ async function ga4Routes(app, opts) {
|
|
|
24446
24724
|
} catch (e) {
|
|
24447
24725
|
const msg = e instanceof Error ? e.message : String(e);
|
|
24448
24726
|
gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
|
|
24449
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
24727
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq28(runs.id, runId)).run();
|
|
24450
24728
|
throw e;
|
|
24451
24729
|
}
|
|
24452
24730
|
});
|
|
@@ -24457,11 +24735,11 @@ async function ga4Routes(app, opts) {
|
|
|
24457
24735
|
const window = parseWindow(request.query.window);
|
|
24458
24736
|
const cutoff = windowCutoff(window);
|
|
24459
24737
|
const cutoffDate = cutoff?.slice(0, 10) ?? null;
|
|
24460
|
-
const snapshotConditions = [
|
|
24738
|
+
const snapshotConditions = [eq28(gaTrafficSnapshots.projectId, project.id)];
|
|
24461
24739
|
if (cutoffDate) snapshotConditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
24462
|
-
const aiConditions = [
|
|
24740
|
+
const aiConditions = [eq28(gaAiReferrals.projectId, project.id)];
|
|
24463
24741
|
if (cutoffDate) aiConditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
24464
|
-
const socialConditions = [
|
|
24742
|
+
const socialConditions = [eq28(gaSocialReferrals.projectId, project.id)];
|
|
24465
24743
|
if (cutoffDate) socialConditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
24466
24744
|
const windowSummaryRow = cutoffDate ? app.db.select({
|
|
24467
24745
|
totalSessions: gaTrafficWindowSummaries.totalSessions,
|
|
@@ -24469,35 +24747,35 @@ async function ga4Routes(app, opts) {
|
|
|
24469
24747
|
totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
|
|
24470
24748
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
24471
24749
|
}).from(gaTrafficWindowSummaries).where(
|
|
24472
|
-
|
|
24473
|
-
|
|
24474
|
-
|
|
24750
|
+
and22(
|
|
24751
|
+
eq28(gaTrafficWindowSummaries.projectId, project.id),
|
|
24752
|
+
eq28(gaTrafficWindowSummaries.windowKey, window)
|
|
24475
24753
|
)
|
|
24476
24754
|
).get() : null;
|
|
24477
24755
|
const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
|
|
24478
24756
|
totalSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
|
|
24479
24757
|
totalOrganicSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
|
|
24480
24758
|
totalUsers: sql10`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
|
|
24481
|
-
}).from(gaTrafficSnapshots).where(
|
|
24759
|
+
}).from(gaTrafficSnapshots).where(and22(...snapshotConditions)).get() : null;
|
|
24482
24760
|
const summaryRow = cutoffDate ? windowSummaryRow ?? snapshotTotalsRow : app.db.select({
|
|
24483
24761
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
24484
24762
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
24485
24763
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
24486
|
-
}).from(gaTrafficSummaries).where(
|
|
24764
|
+
}).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).get();
|
|
24487
24765
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
24488
24766
|
totalDirectSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
24489
|
-
}).from(gaTrafficSnapshots).where(
|
|
24767
|
+
}).from(gaTrafficSnapshots).where(and22(...snapshotConditions)).get();
|
|
24490
24768
|
const summaryMeta = app.db.select({
|
|
24491
24769
|
periodStart: gaTrafficSummaries.periodStart,
|
|
24492
24770
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
24493
|
-
}).from(gaTrafficSummaries).where(
|
|
24771
|
+
}).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).get();
|
|
24494
24772
|
const rows = app.db.select({
|
|
24495
24773
|
landingPage: sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
|
|
24496
24774
|
sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
|
|
24497
24775
|
organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
24498
24776
|
directSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
|
|
24499
24777
|
users: sql10`SUM(${gaTrafficSnapshots.users})`
|
|
24500
|
-
}).from(gaTrafficSnapshots).where(
|
|
24778
|
+
}).from(gaTrafficSnapshots).where(and22(...snapshotConditions)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
|
|
24501
24779
|
const aiReferralRows = app.db.select({
|
|
24502
24780
|
source: gaAiReferrals.source,
|
|
24503
24781
|
medium: gaAiReferrals.medium,
|
|
@@ -24505,7 +24783,7 @@ async function ga4Routes(app, opts) {
|
|
|
24505
24783
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
24506
24784
|
sessions: sql10`SUM(${gaAiReferrals.sessions})`,
|
|
24507
24785
|
users: sql10`SUM(${gaAiReferrals.users})`
|
|
24508
|
-
}).from(gaAiReferrals).where(
|
|
24786
|
+
}).from(gaAiReferrals).where(and22(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
|
|
24509
24787
|
const aiReferralLandingPageRows = app.db.select({
|
|
24510
24788
|
source: gaAiReferrals.source,
|
|
24511
24789
|
medium: gaAiReferrals.medium,
|
|
@@ -24514,7 +24792,7 @@ async function ga4Routes(app, opts) {
|
|
|
24514
24792
|
landingPage: sql10`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
|
|
24515
24793
|
sessions: sql10`SUM(${gaAiReferrals.sessions})`,
|
|
24516
24794
|
users: sql10`SUM(${gaAiReferrals.users})`
|
|
24517
|
-
}).from(gaAiReferrals).where(
|
|
24795
|
+
}).from(gaAiReferrals).where(and22(...aiConditions)).groupBy(
|
|
24518
24796
|
gaAiReferrals.source,
|
|
24519
24797
|
gaAiReferrals.medium,
|
|
24520
24798
|
gaAiReferrals.trafficClass,
|
|
@@ -24530,7 +24808,7 @@ async function ga4Routes(app, opts) {
|
|
|
24530
24808
|
channelGroup: gaAiReferrals.channelGroup,
|
|
24531
24809
|
sessions: gaAiReferrals.sessions,
|
|
24532
24810
|
users: gaAiReferrals.users
|
|
24533
|
-
}).from(gaAiReferrals).where(
|
|
24811
|
+
}).from(gaAiReferrals).where(and22(...aiConditions)).all();
|
|
24534
24812
|
const aiReferrals = pickWinningDimension(
|
|
24535
24813
|
aiReferralRows,
|
|
24536
24814
|
(r) => `${r.source}\0${r.medium}\0${r.trafficClass}`
|
|
@@ -24546,12 +24824,12 @@ async function ga4Routes(app, opts) {
|
|
|
24546
24824
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
24547
24825
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
|
|
24548
24826
|
users: sql10`SUM(${gaSocialReferrals.users})`
|
|
24549
|
-
}).from(gaSocialReferrals).where(
|
|
24827
|
+
}).from(gaSocialReferrals).where(and22(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql10`SUM(${gaSocialReferrals.sessions}) DESC`).all();
|
|
24550
24828
|
const socialTotals = app.db.select({
|
|
24551
24829
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
|
|
24552
24830
|
users: sql10`SUM(${gaSocialReferrals.users})`
|
|
24553
|
-
}).from(gaSocialReferrals).where(
|
|
24554
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
24831
|
+
}).from(gaSocialReferrals).where(and22(...socialConditions)).get();
|
|
24832
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq28(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
24555
24833
|
const total = summaryRow?.totalSessions ?? 0;
|
|
24556
24834
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
24557
24835
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -24649,7 +24927,7 @@ async function ga4Routes(app, opts) {
|
|
|
24649
24927
|
const project = resolveProject(app.db, request.params.name);
|
|
24650
24928
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
24651
24929
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
24652
|
-
const conditions = [
|
|
24930
|
+
const conditions = [eq28(gaAiReferrals.projectId, project.id)];
|
|
24653
24931
|
if (cutoffDate) conditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
24654
24932
|
const rows = app.db.select({
|
|
24655
24933
|
date: gaAiReferrals.date,
|
|
@@ -24660,7 +24938,7 @@ async function ga4Routes(app, opts) {
|
|
|
24660
24938
|
sourceDimension: gaAiReferrals.sourceDimension,
|
|
24661
24939
|
sessions: sql10`SUM(${gaAiReferrals.sessions})`,
|
|
24662
24940
|
users: sql10`SUM(${gaAiReferrals.users})`
|
|
24663
|
-
}).from(gaAiReferrals).where(
|
|
24941
|
+
}).from(gaAiReferrals).where(and22(...conditions)).groupBy(
|
|
24664
24942
|
gaAiReferrals.date,
|
|
24665
24943
|
gaAiReferrals.source,
|
|
24666
24944
|
gaAiReferrals.medium,
|
|
@@ -24674,7 +24952,7 @@ async function ga4Routes(app, opts) {
|
|
|
24674
24952
|
const project = resolveProject(app.db, request.params.name);
|
|
24675
24953
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
24676
24954
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
24677
|
-
const conditions = [
|
|
24955
|
+
const conditions = [eq28(gaSocialReferrals.projectId, project.id)];
|
|
24678
24956
|
if (cutoffDate) conditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
24679
24957
|
const rows = app.db.select({
|
|
24680
24958
|
date: gaSocialReferrals.date,
|
|
@@ -24683,7 +24961,7 @@ async function ga4Routes(app, opts) {
|
|
|
24683
24961
|
channelGroup: gaSocialReferrals.channelGroup,
|
|
24684
24962
|
sessions: gaSocialReferrals.sessions,
|
|
24685
24963
|
users: gaSocialReferrals.users
|
|
24686
|
-
}).from(gaSocialReferrals).where(
|
|
24964
|
+
}).from(gaSocialReferrals).where(and22(...conditions)).orderBy(gaSocialReferrals.date).all();
|
|
24687
24965
|
return rows;
|
|
24688
24966
|
});
|
|
24689
24967
|
app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
|
|
@@ -24696,8 +24974,8 @@ async function ga4Routes(app, opts) {
|
|
|
24696
24974
|
d.setDate(d.getDate() - n);
|
|
24697
24975
|
return fmt(d);
|
|
24698
24976
|
};
|
|
24699
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
24700
|
-
|
|
24977
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and22(
|
|
24978
|
+
eq28(gaSocialReferrals.projectId, project.id),
|
|
24701
24979
|
sql10`${gaSocialReferrals.date} >= ${from}`,
|
|
24702
24980
|
sql10`${gaSocialReferrals.date} < ${to}`
|
|
24703
24981
|
)).get();
|
|
@@ -24709,16 +24987,16 @@ async function ga4Routes(app, opts) {
|
|
|
24709
24987
|
const sourceCurrent = app.db.select({
|
|
24710
24988
|
source: gaSocialReferrals.source,
|
|
24711
24989
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`
|
|
24712
|
-
}).from(gaSocialReferrals).where(
|
|
24713
|
-
|
|
24990
|
+
}).from(gaSocialReferrals).where(and22(
|
|
24991
|
+
eq28(gaSocialReferrals.projectId, project.id),
|
|
24714
24992
|
sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
24715
24993
|
sql10`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
24716
24994
|
)).groupBy(gaSocialReferrals.source).all();
|
|
24717
24995
|
const sourcePrev = app.db.select({
|
|
24718
24996
|
source: gaSocialReferrals.source,
|
|
24719
24997
|
sessions: sql10`SUM(${gaSocialReferrals.sessions})`
|
|
24720
|
-
}).from(gaSocialReferrals).where(
|
|
24721
|
-
|
|
24998
|
+
}).from(gaSocialReferrals).where(and22(
|
|
24999
|
+
eq28(gaSocialReferrals.projectId, project.id),
|
|
24722
25000
|
sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
24723
25001
|
sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
24724
25002
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -24759,16 +25037,16 @@ async function ga4Routes(app, opts) {
|
|
|
24759
25037
|
return fmt(d);
|
|
24760
25038
|
};
|
|
24761
25039
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
24762
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
24763
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
24764
|
-
const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(
|
|
24765
|
-
const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
24766
|
-
|
|
25040
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and22(eq28(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
25041
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and22(eq28(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
25042
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and22(eq28(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
25043
|
+
const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and22(
|
|
25044
|
+
eq28(gaAiReferrals.projectId, project.id),
|
|
24767
25045
|
sql10`${gaAiReferrals.date} >= ${from}`,
|
|
24768
25046
|
sql10`${gaAiReferrals.date} < ${to}`,
|
|
24769
|
-
|
|
25047
|
+
eq28(gaAiReferrals.sourceDimension, "session")
|
|
24770
25048
|
)).get();
|
|
24771
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(
|
|
25049
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and22(eq28(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
|
|
24772
25050
|
const todayStr = fmt(today);
|
|
24773
25051
|
const buildTrend = (sum) => {
|
|
24774
25052
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -24777,17 +25055,17 @@ async function ga4Routes(app, opts) {
|
|
|
24777
25055
|
const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
|
|
24778
25056
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
24779
25057
|
};
|
|
24780
|
-
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
24781
|
-
|
|
25058
|
+
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and22(
|
|
25059
|
+
eq28(gaAiReferrals.projectId, project.id),
|
|
24782
25060
|
sql10`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
24783
25061
|
sql10`${gaAiReferrals.date} < ${todayStr}`,
|
|
24784
|
-
|
|
25062
|
+
eq28(gaAiReferrals.sourceDimension, "session")
|
|
24785
25063
|
)).groupBy(gaAiReferrals.source).all();
|
|
24786
|
-
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(
|
|
24787
|
-
|
|
25064
|
+
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and22(
|
|
25065
|
+
eq28(gaAiReferrals.projectId, project.id),
|
|
24788
25066
|
sql10`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
24789
25067
|
sql10`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
24790
|
-
|
|
25068
|
+
eq28(gaAiReferrals.sourceDimension, "session")
|
|
24791
25069
|
)).groupBy(gaAiReferrals.source).all();
|
|
24792
25070
|
const findBiggestMover = (current, prev) => {
|
|
24793
25071
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
@@ -24803,8 +25081,8 @@ async function ga4Routes(app, opts) {
|
|
|
24803
25081
|
}
|
|
24804
25082
|
return mover;
|
|
24805
25083
|
};
|
|
24806
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
24807
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(
|
|
25084
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and22(eq28(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
25085
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and22(eq28(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
24808
25086
|
return {
|
|
24809
25087
|
total: buildTrend(sumTotal),
|
|
24810
25088
|
organic: buildTrend(sumOrganic),
|
|
@@ -24819,14 +25097,14 @@ async function ga4Routes(app, opts) {
|
|
|
24819
25097
|
const project = resolveProject(app.db, request.params.name);
|
|
24820
25098
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
24821
25099
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
24822
|
-
const conditions = [
|
|
25100
|
+
const conditions = [eq28(gaTrafficSnapshots.projectId, project.id)];
|
|
24823
25101
|
if (cutoffDate) conditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
24824
25102
|
const rows = app.db.select({
|
|
24825
25103
|
date: gaTrafficSnapshots.date,
|
|
24826
25104
|
sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
|
|
24827
25105
|
organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
24828
25106
|
users: sql10`SUM(${gaTrafficSnapshots.users})`
|
|
24829
|
-
}).from(gaTrafficSnapshots).where(
|
|
25107
|
+
}).from(gaTrafficSnapshots).where(and22(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
24830
25108
|
return rows.map((r) => ({
|
|
24831
25109
|
date: r.date,
|
|
24832
25110
|
sessions: r.sessions ?? 0,
|
|
@@ -24842,7 +25120,7 @@ async function ga4Routes(app, opts) {
|
|
|
24842
25120
|
sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
|
|
24843
25121
|
organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
24844
25122
|
users: sql10`SUM(${gaTrafficSnapshots.users})`
|
|
24845
|
-
}).from(gaTrafficSnapshots).where(
|
|
25123
|
+
}).from(gaTrafficSnapshots).where(eq28(gaTrafficSnapshots.projectId, project.id)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
24846
25124
|
return {
|
|
24847
25125
|
pages: trafficPages.map((r) => ({
|
|
24848
25126
|
landingPage: r.landingPage,
|
|
@@ -26490,7 +26768,7 @@ async function wordpressRoutes(app, opts) {
|
|
|
26490
26768
|
|
|
26491
26769
|
// ../api-routes/src/backlinks.ts
|
|
26492
26770
|
import crypto24 from "crypto";
|
|
26493
|
-
import { and as
|
|
26771
|
+
import { and as and24, asc as asc5, desc as desc14, eq as eq29, sql as sql11 } from "drizzle-orm";
|
|
26494
26772
|
|
|
26495
26773
|
// ../integration-commoncrawl/src/constants.ts
|
|
26496
26774
|
import os2 from "os";
|
|
@@ -26893,7 +27171,7 @@ function pruneCachedRelease(release, opts = {}) {
|
|
|
26893
27171
|
}
|
|
26894
27172
|
|
|
26895
27173
|
// ../api-routes/src/backlinks-filter.ts
|
|
26896
|
-
import { and as
|
|
27174
|
+
import { and as and23, ne as ne3, notLike } from "drizzle-orm";
|
|
26897
27175
|
var BACKLINK_FILTER_PATTERNS = [
|
|
26898
27176
|
"*.google.com",
|
|
26899
27177
|
"*.googleusercontent.com",
|
|
@@ -26916,7 +27194,7 @@ function backlinkCrawlerExclusionClause() {
|
|
|
26916
27194
|
conditions.push(ne3(backlinkDomains.linkingDomain, pattern));
|
|
26917
27195
|
}
|
|
26918
27196
|
}
|
|
26919
|
-
const combined =
|
|
27197
|
+
const combined = and23(...conditions);
|
|
26920
27198
|
if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
|
|
26921
27199
|
return combined;
|
|
26922
27200
|
}
|
|
@@ -26987,11 +27265,11 @@ function mapRunRow(row) {
|
|
|
26987
27265
|
}
|
|
26988
27266
|
function latestSummaryForProject(db, projectId, source, release) {
|
|
26989
27267
|
const conditions = [
|
|
26990
|
-
|
|
26991
|
-
|
|
27268
|
+
eq29(backlinkSummaries.projectId, projectId),
|
|
27269
|
+
eq29(backlinkSummaries.source, source)
|
|
26992
27270
|
];
|
|
26993
|
-
if (release) conditions.push(
|
|
26994
|
-
return db.select().from(backlinkSummaries).where(
|
|
27271
|
+
if (release) conditions.push(eq29(backlinkSummaries.release, release));
|
|
27272
|
+
return db.select().from(backlinkSummaries).where(and24(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
|
|
26995
27273
|
}
|
|
26996
27274
|
function parseExcludeCrawlers(value) {
|
|
26997
27275
|
if (!value) return false;
|
|
@@ -26999,12 +27277,12 @@ function parseExcludeCrawlers(value) {
|
|
|
26999
27277
|
return lower === "1" || lower === "true" || lower === "yes";
|
|
27000
27278
|
}
|
|
27001
27279
|
function computeFilteredSummary(db, base) {
|
|
27002
|
-
const baseDomainCondition =
|
|
27003
|
-
|
|
27004
|
-
|
|
27005
|
-
|
|
27280
|
+
const baseDomainCondition = and24(
|
|
27281
|
+
eq29(backlinkDomains.projectId, base.projectId),
|
|
27282
|
+
eq29(backlinkDomains.source, base.source),
|
|
27283
|
+
eq29(backlinkDomains.release, base.release)
|
|
27006
27284
|
);
|
|
27007
|
-
const filteredCondition =
|
|
27285
|
+
const filteredCondition = and24(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
27008
27286
|
const unfilteredAgg = db.select({
|
|
27009
27287
|
count: sql11`count(*)`,
|
|
27010
27288
|
total: sql11`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
@@ -27034,13 +27312,13 @@ function computeFilteredSummary(db, base) {
|
|
|
27034
27312
|
};
|
|
27035
27313
|
}
|
|
27036
27314
|
function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
|
|
27037
|
-
const summary = db.select().from(backlinkSummaries).where(
|
|
27315
|
+
const summary = db.select().from(backlinkSummaries).where(and24(eq29(backlinkSummaries.projectId, projectId), eq29(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
|
|
27038
27316
|
let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
|
|
27039
27317
|
if (summary && excludeCrawlers) {
|
|
27040
|
-
const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(
|
|
27041
|
-
|
|
27042
|
-
|
|
27043
|
-
|
|
27318
|
+
const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(and24(
|
|
27319
|
+
eq29(backlinkDomains.projectId, projectId),
|
|
27320
|
+
eq29(backlinkDomains.source, source),
|
|
27321
|
+
eq29(backlinkDomains.release, summary.release),
|
|
27044
27322
|
backlinkCrawlerExclusionClause()
|
|
27045
27323
|
)).get();
|
|
27046
27324
|
totalLinkingDomains = Number(filtered?.count ?? 0);
|
|
@@ -27055,7 +27333,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
|
|
|
27055
27333
|
};
|
|
27056
27334
|
}
|
|
27057
27335
|
function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
|
|
27058
|
-
const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(
|
|
27336
|
+
const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
|
|
27059
27337
|
const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
|
|
27060
27338
|
const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
|
|
27061
27339
|
const sources = [
|
|
@@ -27111,7 +27389,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
27111
27389
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
27112
27390
|
);
|
|
27113
27391
|
}
|
|
27114
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
27392
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.release, release)).get();
|
|
27115
27393
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27116
27394
|
if (existing) {
|
|
27117
27395
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -27122,9 +27400,9 @@ async function backlinksRoutes(app, opts) {
|
|
|
27122
27400
|
phaseDetail: null,
|
|
27123
27401
|
error: null,
|
|
27124
27402
|
updatedAt: now
|
|
27125
|
-
}).where(
|
|
27403
|
+
}).where(eq29(ccReleaseSyncs.id, existing.id)).run();
|
|
27126
27404
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
27127
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
27405
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.id, existing.id)).get();
|
|
27128
27406
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
27129
27407
|
}
|
|
27130
27408
|
const id = crypto24.randomUUID();
|
|
@@ -27136,7 +27414,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
27136
27414
|
updatedAt: now
|
|
27137
27415
|
}).run();
|
|
27138
27416
|
opts.onReleaseSyncRequested(id, release);
|
|
27139
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
27417
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.id, id)).get();
|
|
27140
27418
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
27141
27419
|
});
|
|
27142
27420
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
@@ -27194,7 +27472,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
27194
27472
|
createdAt: now
|
|
27195
27473
|
}).run();
|
|
27196
27474
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
27197
|
-
const run = app.db.select().from(runs).where(
|
|
27475
|
+
const run = app.db.select().from(runs).where(eq29(runs.id, runId)).get();
|
|
27198
27476
|
return reply.status(201).send(mapRunRow(run));
|
|
27199
27477
|
});
|
|
27200
27478
|
app.get(
|
|
@@ -27220,12 +27498,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
27220
27498
|
const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
|
|
27221
27499
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
27222
27500
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
27223
|
-
const baseDomainCondition =
|
|
27224
|
-
|
|
27225
|
-
|
|
27226
|
-
|
|
27501
|
+
const baseDomainCondition = and24(
|
|
27502
|
+
eq29(backlinkDomains.projectId, project.id),
|
|
27503
|
+
eq29(backlinkDomains.source, source),
|
|
27504
|
+
eq29(backlinkDomains.release, targetRelease)
|
|
27227
27505
|
);
|
|
27228
|
-
const domainCondition = excludeCrawlers ?
|
|
27506
|
+
const domainCondition = excludeCrawlers ? and24(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
27229
27507
|
const totalRow = app.db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
27230
27508
|
const rows = app.db.select({
|
|
27231
27509
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
@@ -27249,7 +27527,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
27249
27527
|
async (request, reply) => {
|
|
27250
27528
|
const project = resolveProject(app.db, request.params.name);
|
|
27251
27529
|
const source = parseSourceParam(request.query.source);
|
|
27252
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
27530
|
+
const rows = app.db.select().from(backlinkSummaries).where(and24(eq29(backlinkSummaries.projectId, project.id), eq29(backlinkSummaries.source, source))).orderBy(asc5(backlinkSummaries.queriedAt)).all();
|
|
27253
27531
|
const response = rows.map((r) => ({
|
|
27254
27532
|
release: r.release,
|
|
27255
27533
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -27296,7 +27574,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
27296
27574
|
createdAt: now
|
|
27297
27575
|
}).run();
|
|
27298
27576
|
opts.onBingBacklinkSyncRequested(runId, project.id);
|
|
27299
|
-
const run = app.db.select().from(runs).where(
|
|
27577
|
+
const run = app.db.select().from(runs).where(eq29(runs.id, runId)).get();
|
|
27300
27578
|
return reply.status(201).send(mapRunRow(run));
|
|
27301
27579
|
}
|
|
27302
27580
|
);
|
|
@@ -27305,7 +27583,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
27305
27583
|
// ../api-routes/src/traffic.ts
|
|
27306
27584
|
import crypto26 from "crypto";
|
|
27307
27585
|
import { Agent as UndiciAgent } from "undici";
|
|
27308
|
-
import { and as
|
|
27586
|
+
import { and as and25, desc as desc15, eq as eq30, gte as gte5, lte as lte4, sql as sql12 } from "drizzle-orm";
|
|
27309
27587
|
|
|
27310
27588
|
// ../integration-cloud-run/src/auth.ts
|
|
27311
27589
|
import crypto25 from "crypto";
|
|
@@ -31172,8 +31450,8 @@ async function runBackfillTask(options) {
|
|
|
31172
31450
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
31173
31451
|
try {
|
|
31174
31452
|
app.db.transaction((tx) => {
|
|
31175
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
31176
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
31453
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq30(runs.id, runId)).run();
|
|
31454
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq30(trafficSources.id, sourceRow.id)).run();
|
|
31177
31455
|
});
|
|
31178
31456
|
} catch {
|
|
31179
31457
|
}
|
|
@@ -31188,7 +31466,7 @@ async function runBackfillTask(options) {
|
|
|
31188
31466
|
if (allEvents.length === 0) {
|
|
31189
31467
|
const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
31190
31468
|
try {
|
|
31191
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(
|
|
31469
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq30(runs.id, runId)).run();
|
|
31192
31470
|
} catch {
|
|
31193
31471
|
}
|
|
31194
31472
|
return;
|
|
@@ -31210,31 +31488,31 @@ async function runBackfillTask(options) {
|
|
|
31210
31488
|
try {
|
|
31211
31489
|
app.db.transaction((tx) => {
|
|
31212
31490
|
tx.delete(crawlerEventsHourly).where(
|
|
31213
|
-
|
|
31214
|
-
|
|
31215
|
-
|
|
31216
|
-
|
|
31491
|
+
and25(
|
|
31492
|
+
eq30(crawlerEventsHourly.sourceId, sourceRow.id),
|
|
31493
|
+
gte5(crawlerEventsHourly.tsHour, windowStartIso),
|
|
31494
|
+
lte4(crawlerEventsHourly.tsHour, windowEndIso)
|
|
31217
31495
|
)
|
|
31218
31496
|
).run();
|
|
31219
31497
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
31220
|
-
|
|
31221
|
-
|
|
31222
|
-
|
|
31223
|
-
|
|
31498
|
+
and25(
|
|
31499
|
+
eq30(aiUserFetchEventsHourly.sourceId, sourceRow.id),
|
|
31500
|
+
gte5(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
31501
|
+
lte4(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
31224
31502
|
)
|
|
31225
31503
|
).run();
|
|
31226
31504
|
tx.delete(aiReferralEventsHourly).where(
|
|
31227
|
-
|
|
31228
|
-
|
|
31229
|
-
|
|
31230
|
-
|
|
31505
|
+
and25(
|
|
31506
|
+
eq30(aiReferralEventsHourly.sourceId, sourceRow.id),
|
|
31507
|
+
gte5(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
31508
|
+
lte4(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
31231
31509
|
)
|
|
31232
31510
|
).run();
|
|
31233
31511
|
tx.delete(rawEventSamples).where(
|
|
31234
|
-
|
|
31235
|
-
|
|
31236
|
-
|
|
31237
|
-
|
|
31512
|
+
and25(
|
|
31513
|
+
eq30(rawEventSamples.sourceId, sourceRow.id),
|
|
31514
|
+
gte5(rawEventSamples.ts, windowStartIso),
|
|
31515
|
+
lte4(rawEventSamples.ts, windowEndIso)
|
|
31238
31516
|
)
|
|
31239
31517
|
).run();
|
|
31240
31518
|
for (const bucket of report.crawlerEventsHourly) {
|
|
@@ -31323,8 +31601,8 @@ async function runBackfillTask(options) {
|
|
|
31323
31601
|
lastError: null,
|
|
31324
31602
|
lastEventIds: newRingBuffer,
|
|
31325
31603
|
updatedAt: finishedAt
|
|
31326
|
-
}).where(
|
|
31327
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
31604
|
+
}).where(eq30(trafficSources.id, sourceRow.id)).run();
|
|
31605
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq30(runs.id, runId)).run();
|
|
31328
31606
|
});
|
|
31329
31607
|
} catch (e) {
|
|
31330
31608
|
markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -31406,7 +31684,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31406
31684
|
createdAt: existing?.createdAt ?? now,
|
|
31407
31685
|
updatedAt: now
|
|
31408
31686
|
});
|
|
31409
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
31687
|
+
const activeSource = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
31410
31688
|
const config = {
|
|
31411
31689
|
gcpProjectId,
|
|
31412
31690
|
serviceName: serviceName ?? null,
|
|
@@ -31422,8 +31700,8 @@ async function trafficRoutes(app, opts) {
|
|
|
31422
31700
|
lastError: null,
|
|
31423
31701
|
configJson: config,
|
|
31424
31702
|
updatedAt: now
|
|
31425
|
-
}).where(
|
|
31426
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
31703
|
+
}).where(eq30(trafficSources.id, activeSource.id)).run();
|
|
31704
|
+
sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, activeSource.id)).get();
|
|
31427
31705
|
} else {
|
|
31428
31706
|
const newId = crypto26.randomUUID();
|
|
31429
31707
|
app.db.insert(trafficSources).values({
|
|
@@ -31440,7 +31718,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31440
31718
|
createdAt: now,
|
|
31441
31719
|
updatedAt: now
|
|
31442
31720
|
}).run();
|
|
31443
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
31721
|
+
sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, newId)).get();
|
|
31444
31722
|
}
|
|
31445
31723
|
writeAuditLog(app.db, {
|
|
31446
31724
|
projectId: project.id,
|
|
@@ -31492,7 +31770,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31492
31770
|
createdAt: existing?.createdAt ?? now,
|
|
31493
31771
|
updatedAt: now
|
|
31494
31772
|
});
|
|
31495
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
31773
|
+
const activeSource = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
31496
31774
|
const config = { baseUrl, username };
|
|
31497
31775
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
31498
31776
|
let sourceRow;
|
|
@@ -31503,8 +31781,8 @@ async function trafficRoutes(app, opts) {
|
|
|
31503
31781
|
lastError: null,
|
|
31504
31782
|
configJson: config,
|
|
31505
31783
|
updatedAt: now
|
|
31506
|
-
}).where(
|
|
31507
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
31784
|
+
}).where(eq30(trafficSources.id, activeSource.id)).run();
|
|
31785
|
+
sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, activeSource.id)).get();
|
|
31508
31786
|
} else {
|
|
31509
31787
|
const newId = crypto26.randomUUID();
|
|
31510
31788
|
app.db.insert(trafficSources).values({
|
|
@@ -31521,7 +31799,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31521
31799
|
createdAt: now,
|
|
31522
31800
|
updatedAt: now
|
|
31523
31801
|
}).run();
|
|
31524
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
31802
|
+
sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, newId)).get();
|
|
31525
31803
|
}
|
|
31526
31804
|
writeAuditLog(app.db, {
|
|
31527
31805
|
projectId: project.id,
|
|
@@ -31575,7 +31853,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31575
31853
|
createdAt: existing?.createdAt ?? now,
|
|
31576
31854
|
updatedAt: now
|
|
31577
31855
|
});
|
|
31578
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
31856
|
+
const activeSource = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
|
|
31579
31857
|
const config = { projectId, teamId, environment };
|
|
31580
31858
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
31581
31859
|
const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
|
|
@@ -31587,8 +31865,8 @@ async function trafficRoutes(app, opts) {
|
|
|
31587
31865
|
lastError: null,
|
|
31588
31866
|
configJson: config,
|
|
31589
31867
|
updatedAt: now
|
|
31590
|
-
}).where(
|
|
31591
|
-
row = tx.select().from(trafficSources).where(
|
|
31868
|
+
}).where(eq30(trafficSources.id, activeSource.id)).run();
|
|
31869
|
+
row = tx.select().from(trafficSources).where(eq30(trafficSources.id, activeSource.id)).get();
|
|
31592
31870
|
} else {
|
|
31593
31871
|
const newId = crypto26.randomUUID();
|
|
31594
31872
|
tx.insert(trafficSources).values({
|
|
@@ -31613,12 +31891,12 @@ async function trafficRoutes(app, opts) {
|
|
|
31613
31891
|
createdAt: now,
|
|
31614
31892
|
updatedAt: now
|
|
31615
31893
|
}).run();
|
|
31616
|
-
row = tx.select().from(trafficSources).where(
|
|
31894
|
+
row = tx.select().from(trafficSources).where(eq30(trafficSources.id, newId)).get();
|
|
31617
31895
|
}
|
|
31618
31896
|
const existingSchedule = tx.select().from(schedules).where(
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31897
|
+
and25(
|
|
31898
|
+
eq30(schedules.projectId, project.id),
|
|
31899
|
+
eq30(schedules.kind, SchedulableRunKinds["traffic-sync"])
|
|
31622
31900
|
)
|
|
31623
31901
|
).get();
|
|
31624
31902
|
let created = false;
|
|
@@ -31667,7 +31945,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31667
31945
|
});
|
|
31668
31946
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
31669
31947
|
const project = resolveProject(app.db, request.params.name);
|
|
31670
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
31948
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, request.params.id)).get();
|
|
31671
31949
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
31672
31950
|
throw notFound("Traffic source", request.params.id);
|
|
31673
31951
|
}
|
|
@@ -31693,8 +31971,8 @@ async function trafficRoutes(app, opts) {
|
|
|
31693
31971
|
const markFailed = (msg, errorCode) => {
|
|
31694
31972
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
31695
31973
|
app.db.transaction((tx) => {
|
|
31696
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
31697
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
31974
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq30(runs.id, runId)).run();
|
|
31975
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq30(trafficSources.id, sourceRow.id)).run();
|
|
31698
31976
|
});
|
|
31699
31977
|
try {
|
|
31700
31978
|
opts.onTrafficSynced?.({
|
|
@@ -31775,7 +32053,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31775
32053
|
}
|
|
31776
32054
|
const credential = credentialStore.getConnection(project.name);
|
|
31777
32055
|
if (!credential) {
|
|
31778
|
-
app.db.delete(runs).where(
|
|
32056
|
+
app.db.delete(runs).where(eq30(runs.id, runId)).run();
|
|
31779
32057
|
throw validationError(
|
|
31780
32058
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
31781
32059
|
);
|
|
@@ -31824,12 +32102,12 @@ async function trafficRoutes(app, opts) {
|
|
|
31824
32102
|
auditAction = "traffic.vercel.synced";
|
|
31825
32103
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
31826
32104
|
if (!credentialStore) {
|
|
31827
|
-
app.db.delete(runs).where(
|
|
32105
|
+
app.db.delete(runs).where(eq30(runs.id, runId)).run();
|
|
31828
32106
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
31829
32107
|
}
|
|
31830
32108
|
const credential = credentialStore.getConnection(project.name);
|
|
31831
32109
|
if (!credential) {
|
|
31832
|
-
app.db.delete(runs).where(
|
|
32110
|
+
app.db.delete(runs).where(eq30(runs.id, runId)).run();
|
|
31833
32111
|
throw validationError(
|
|
31834
32112
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
31835
32113
|
);
|
|
@@ -31929,7 +32207,7 @@ async function trafficRoutes(app, opts) {
|
|
|
31929
32207
|
let aiReferralHitsCount = 0;
|
|
31930
32208
|
let unknownHitsCount = 0;
|
|
31931
32209
|
app.db.transaction((tx) => {
|
|
31932
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
32210
|
+
const latestRow = tx.select().from(trafficSources).where(eq30(trafficSources.id, sourceRow.id)).get();
|
|
31933
32211
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
31934
32212
|
const seenEventIds = new Set(previousIds);
|
|
31935
32213
|
const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
|
|
@@ -32104,8 +32382,8 @@ async function trafficRoutes(app, opts) {
|
|
|
32104
32382
|
if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
|
|
32105
32383
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
32106
32384
|
}
|
|
32107
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
32108
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
32385
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq30(trafficSources.id, sourceRow.id)).run();
|
|
32386
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq30(runs.id, runId)).run();
|
|
32109
32387
|
});
|
|
32110
32388
|
writeAuditLog(app.db, {
|
|
32111
32389
|
projectId: project.id,
|
|
@@ -32157,7 +32435,7 @@ async function trafficRoutes(app, opts) {
|
|
|
32157
32435
|
});
|
|
32158
32436
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
|
|
32159
32437
|
const project = resolveProject(app.db, request.params.name);
|
|
32160
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
32438
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq30(trafficSources.id, request.params.id)).get();
|
|
32161
32439
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
32162
32440
|
throw notFound("Traffic source", request.params.id);
|
|
32163
32441
|
}
|
|
@@ -32345,9 +32623,9 @@ async function trafficRoutes(app, opts) {
|
|
|
32345
32623
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
32346
32624
|
hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
32347
32625
|
}).from(crawlerEventsHourly).where(
|
|
32348
|
-
|
|
32349
|
-
|
|
32350
|
-
|
|
32626
|
+
and25(
|
|
32627
|
+
eq30(crawlerEventsHourly.sourceId, row.id),
|
|
32628
|
+
gte5(crawlerEventsHourly.tsHour, since)
|
|
32351
32629
|
)
|
|
32352
32630
|
).groupBy(crawlerEventsHourly.pathNormalized).all();
|
|
32353
32631
|
const crawlerSegments = segmentCrawlerHits(
|
|
@@ -32355,28 +32633,28 @@ async function trafficRoutes(app, opts) {
|
|
|
32355
32633
|
);
|
|
32356
32634
|
const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
|
|
32357
32635
|
const aiUserFetchTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
32358
|
-
|
|
32359
|
-
|
|
32360
|
-
|
|
32636
|
+
and25(
|
|
32637
|
+
eq30(aiUserFetchEventsHourly.sourceId, row.id),
|
|
32638
|
+
gte5(aiUserFetchEventsHourly.tsHour, since)
|
|
32361
32639
|
)
|
|
32362
32640
|
).get();
|
|
32363
32641
|
const aiTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
32364
|
-
|
|
32365
|
-
|
|
32366
|
-
|
|
32642
|
+
and25(
|
|
32643
|
+
eq30(aiReferralEventsHourly.sourceId, row.id),
|
|
32644
|
+
gte5(aiReferralEventsHourly.tsHour, since)
|
|
32367
32645
|
)
|
|
32368
32646
|
).get();
|
|
32369
32647
|
const sampleTotals = app.db.select({ total: sql12`COUNT(*)` }).from(rawEventSamples).where(
|
|
32370
|
-
|
|
32371
|
-
|
|
32372
|
-
|
|
32648
|
+
and25(
|
|
32649
|
+
eq30(rawEventSamples.sourceId, row.id),
|
|
32650
|
+
gte5(rawEventSamples.ts, since)
|
|
32373
32651
|
)
|
|
32374
32652
|
).get();
|
|
32375
32653
|
const latestRun = app.db.select().from(runs).where(
|
|
32376
|
-
|
|
32377
|
-
|
|
32378
|
-
|
|
32379
|
-
|
|
32654
|
+
and25(
|
|
32655
|
+
eq30(runs.projectId, projectId),
|
|
32656
|
+
eq30(runs.kind, RunKinds["traffic-sync"]),
|
|
32657
|
+
eq30(runs.sourceId, row.id)
|
|
32380
32658
|
)
|
|
32381
32659
|
).orderBy(desc15(runs.startedAt)).limit(1).get();
|
|
32382
32660
|
return {
|
|
@@ -32407,7 +32685,7 @@ async function trafficRoutes(app, opts) {
|
|
|
32407
32685
|
"`advanceToNow` must be `true`. There is no implicit reset."
|
|
32408
32686
|
);
|
|
32409
32687
|
}
|
|
32410
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
32688
|
+
const sourceRow = app.db.select().from(trafficSources).where(and25(eq30(trafficSources.projectId, project.id), eq30(trafficSources.id, request.params.id))).get();
|
|
32411
32689
|
if (!sourceRow) {
|
|
32412
32690
|
throw notFound("traffic source", request.params.id);
|
|
32413
32691
|
}
|
|
@@ -32424,7 +32702,7 @@ async function trafficRoutes(app, opts) {
|
|
|
32424
32702
|
status: TrafficSourceStatuses.connected,
|
|
32425
32703
|
lastError: null,
|
|
32426
32704
|
updatedAt: now
|
|
32427
|
-
}).where(
|
|
32705
|
+
}).where(eq30(trafficSources.id, sourceRow.id)).run();
|
|
32428
32706
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
32429
32707
|
projectId: project.id,
|
|
32430
32708
|
actor: "api",
|
|
@@ -32432,20 +32710,20 @@ async function trafficRoutes(app, opts) {
|
|
|
32432
32710
|
entityType: "traffic_source",
|
|
32433
32711
|
entityId: sourceRow.id
|
|
32434
32712
|
}));
|
|
32435
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
32713
|
+
updatedRow = tx.select().from(trafficSources).where(eq30(trafficSources.id, sourceRow.id)).get();
|
|
32436
32714
|
});
|
|
32437
32715
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
32438
32716
|
});
|
|
32439
32717
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
32440
32718
|
const project = resolveProject(app.db, request.params.name);
|
|
32441
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
32719
|
+
const rows = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
|
|
32442
32720
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
32443
32721
|
const response = { sources };
|
|
32444
32722
|
return response;
|
|
32445
32723
|
});
|
|
32446
32724
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
32447
32725
|
const project = resolveProject(app.db, request.params.name);
|
|
32448
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
32726
|
+
const rows = app.db.select().from(trafficSources).where(eq30(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
|
|
32449
32727
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
32450
32728
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
32451
32729
|
const response = { sources };
|
|
@@ -32455,7 +32733,7 @@ async function trafficRoutes(app, opts) {
|
|
|
32455
32733
|
"/projects/:name/traffic/sources/:id",
|
|
32456
32734
|
async (request) => {
|
|
32457
32735
|
const project = resolveProject(app.db, request.params.name);
|
|
32458
|
-
const row = app.db.select().from(trafficSources).where(
|
|
32736
|
+
const row = app.db.select().from(trafficSources).where(eq30(trafficSources.id, request.params.id)).get();
|
|
32459
32737
|
if (!row || row.projectId !== project.id) {
|
|
32460
32738
|
throw notFound("Traffic source", request.params.id);
|
|
32461
32739
|
}
|
|
@@ -32507,12 +32785,12 @@ async function trafficRoutes(app, opts) {
|
|
|
32507
32785
|
let aiReferralCounts = aiReferralClassCounts(0, 0, 0);
|
|
32508
32786
|
if (kind === "all" || kind === TrafficEventKinds.crawler) {
|
|
32509
32787
|
const crawlerFilters = [
|
|
32510
|
-
|
|
32511
|
-
|
|
32512
|
-
|
|
32788
|
+
eq30(crawlerEventsHourly.projectId, project.id),
|
|
32789
|
+
gte5(crawlerEventsHourly.tsHour, sinceIso),
|
|
32790
|
+
lte4(crawlerEventsHourly.tsHour, untilIso)
|
|
32513
32791
|
];
|
|
32514
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
32515
|
-
const crawlerWhere =
|
|
32792
|
+
if (sourceIdParam) crawlerFilters.push(eq30(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
32793
|
+
const crawlerWhere = and25(...crawlerFilters);
|
|
32516
32794
|
const pathTotals = app.db.select({
|
|
32517
32795
|
pathNormalized: crawlerEventsHourly.pathNormalized,
|
|
32518
32796
|
hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
|
|
@@ -32539,12 +32817,12 @@ async function trafficRoutes(app, opts) {
|
|
|
32539
32817
|
}
|
|
32540
32818
|
if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
|
|
32541
32819
|
const userFetchFilters = [
|
|
32542
|
-
|
|
32543
|
-
|
|
32544
|
-
|
|
32820
|
+
eq30(aiUserFetchEventsHourly.projectId, project.id),
|
|
32821
|
+
gte5(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
32822
|
+
lte4(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
32545
32823
|
];
|
|
32546
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
32547
|
-
const userFetchWhere =
|
|
32824
|
+
if (sourceIdParam) userFetchFilters.push(eq30(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
32825
|
+
const userFetchWhere = and25(...userFetchFilters);
|
|
32548
32826
|
const total = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
|
|
32549
32827
|
aiUserFetchTotal = Number(total?.total ?? 0);
|
|
32550
32828
|
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc15(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
|
|
@@ -32564,12 +32842,12 @@ async function trafficRoutes(app, opts) {
|
|
|
32564
32842
|
}
|
|
32565
32843
|
if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
|
|
32566
32844
|
const aiFilters = [
|
|
32567
|
-
|
|
32568
|
-
|
|
32569
|
-
|
|
32845
|
+
eq30(aiReferralEventsHourly.projectId, project.id),
|
|
32846
|
+
gte5(aiReferralEventsHourly.tsHour, sinceIso),
|
|
32847
|
+
lte4(aiReferralEventsHourly.tsHour, untilIso)
|
|
32570
32848
|
];
|
|
32571
|
-
if (sourceIdParam) aiFilters.push(
|
|
32572
|
-
const aiWhere =
|
|
32849
|
+
if (sourceIdParam) aiFilters.push(eq30(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
32850
|
+
const aiWhere = and25(...aiFilters);
|
|
32573
32851
|
const total = app.db.select({
|
|
32574
32852
|
total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
|
|
32575
32853
|
paid: sql12`COALESCE(SUM(${aiReferralEventsHourly.paidSessionsOrHits}), 0)`,
|
|
@@ -32791,7 +33069,7 @@ function readInstalledManifest(skillDir) {
|
|
|
32791
33069
|
var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
|
|
32792
33070
|
|
|
32793
33071
|
// ../api-routes/src/doctor/checks/backlinks.ts
|
|
32794
|
-
import { and as
|
|
33072
|
+
import { and as and26, eq as eq31 } from "drizzle-orm";
|
|
32795
33073
|
function skippedNoProject() {
|
|
32796
33074
|
return {
|
|
32797
33075
|
status: CheckStatuses.skipped,
|
|
@@ -32807,8 +33085,8 @@ var BACKLINKS_CHECKS = [
|
|
|
32807
33085
|
title: "Backlinks source connected",
|
|
32808
33086
|
run: (ctx) => {
|
|
32809
33087
|
if (!ctx.project) return skippedNoProject();
|
|
32810
|
-
const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(
|
|
32811
|
-
const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(
|
|
33088
|
+
const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq31(projects.id, ctx.project.id)).get();
|
|
33089
|
+
const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq31(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
|
|
32812
33090
|
const ccConnected = projectRow?.autoExtract === true && !!readySync;
|
|
32813
33091
|
const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
|
|
32814
33092
|
const connected = [];
|
|
@@ -32823,9 +33101,9 @@ var BACKLINKS_CHECKS = [
|
|
|
32823
33101
|
details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
|
|
32824
33102
|
};
|
|
32825
33103
|
}
|
|
32826
|
-
const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(
|
|
32827
|
-
|
|
32828
|
-
|
|
33104
|
+
const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and26(
|
|
33105
|
+
eq31(backlinkSummaries.projectId, ctx.project.id),
|
|
33106
|
+
eq31(backlinkSummaries.source, BacklinkSources.commoncrawl)
|
|
32829
33107
|
)).limit(1).get() : false;
|
|
32830
33108
|
return {
|
|
32831
33109
|
status: CheckStatuses.ok,
|
|
@@ -32985,7 +33263,7 @@ var BING_AUTH_CHECKS = [
|
|
|
32985
33263
|
];
|
|
32986
33264
|
|
|
32987
33265
|
// ../api-routes/src/doctor/checks/content.ts
|
|
32988
|
-
import { eq as
|
|
33266
|
+
import { eq as eq32 } from "drizzle-orm";
|
|
32989
33267
|
var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
|
|
32990
33268
|
var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
|
|
32991
33269
|
function skippedNoProject2() {
|
|
@@ -32998,7 +33276,7 @@ function skippedNoProject2() {
|
|
|
32998
33276
|
}
|
|
32999
33277
|
function loadProject(ctx) {
|
|
33000
33278
|
if (!ctx.project) return null;
|
|
33001
|
-
return ctx.db.select().from(projects).where(
|
|
33279
|
+
return ctx.db.select().from(projects).where(eq32(projects.id, ctx.project.id)).get() ?? null;
|
|
33002
33280
|
}
|
|
33003
33281
|
function percent(value) {
|
|
33004
33282
|
return Math.round(value * 100);
|
|
@@ -33090,7 +33368,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
|
|
|
33090
33368
|
);
|
|
33091
33369
|
|
|
33092
33370
|
// ../api-routes/src/doctor/checks/ads.ts
|
|
33093
|
-
import { eq as
|
|
33371
|
+
import { eq as eq33 } from "drizzle-orm";
|
|
33094
33372
|
var RECENT_SYNC_WARN_DAYS = 7;
|
|
33095
33373
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
33096
33374
|
var adsConnectionCheck = {
|
|
@@ -33107,7 +33385,7 @@ var adsConnectionCheck = {
|
|
|
33107
33385
|
remediation: null
|
|
33108
33386
|
};
|
|
33109
33387
|
}
|
|
33110
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
33388
|
+
const row = ctx.db.select().from(adsConnections).where(eq33(adsConnections.projectId, ctx.project.id)).get();
|
|
33111
33389
|
if (!row) {
|
|
33112
33390
|
return {
|
|
33113
33391
|
status: CheckStatuses.skipped,
|
|
@@ -33157,7 +33435,7 @@ var adsRecentSyncCheck = {
|
|
|
33157
33435
|
remediation: null
|
|
33158
33436
|
};
|
|
33159
33437
|
}
|
|
33160
|
-
const row = ctx.db.select().from(adsConnections).where(
|
|
33438
|
+
const row = ctx.db.select().from(adsConnections).where(eq33(adsConnections.projectId, ctx.project.id)).get();
|
|
33161
33439
|
if (!row) {
|
|
33162
33440
|
return {
|
|
33163
33441
|
status: CheckStatuses.skipped,
|
|
@@ -33348,7 +33626,7 @@ var ga4ConnectionCheck = {
|
|
|
33348
33626
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
33349
33627
|
|
|
33350
33628
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
33351
|
-
import { and as
|
|
33629
|
+
import { and as and27, eq as eq34 } from "drizzle-orm";
|
|
33352
33630
|
var RECENT_SYNC_WARN_DAYS2 = 7;
|
|
33353
33631
|
var RECENT_SYNC_FAIL_DAYS2 = 30;
|
|
33354
33632
|
function skippedNoProject3() {
|
|
@@ -33581,7 +33859,7 @@ var recentSyncCheck = {
|
|
|
33581
33859
|
title: "GBP recent sync",
|
|
33582
33860
|
run: (ctx) => {
|
|
33583
33861
|
if (!ctx.project) return skippedNoProject3();
|
|
33584
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(
|
|
33862
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and27(eq34(gbpLocations.projectId, ctx.project.id), eq34(gbpLocations.selected, true))).all();
|
|
33585
33863
|
if (selected.length === 0) {
|
|
33586
33864
|
return {
|
|
33587
33865
|
status: CheckStatuses.skipped,
|
|
@@ -33641,7 +33919,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
33641
33919
|
);
|
|
33642
33920
|
|
|
33643
33921
|
// ../api-routes/src/doctor/checks/places.ts
|
|
33644
|
-
import { eq as
|
|
33922
|
+
import { eq as eq35 } from "drizzle-orm";
|
|
33645
33923
|
var apiKeyCheck = {
|
|
33646
33924
|
id: "gbp.places.api-key",
|
|
33647
33925
|
category: CheckCategories.auth,
|
|
@@ -33686,7 +33964,7 @@ var apiKeyCheck = {
|
|
|
33686
33964
|
details: { tier: cfg.tier }
|
|
33687
33965
|
};
|
|
33688
33966
|
}
|
|
33689
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
33967
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq35(gbpLocations.projectId, ctx.project.id)).all();
|
|
33690
33968
|
const selected = rows.filter((r) => r.selected);
|
|
33691
33969
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
33692
33970
|
const details = {
|
|
@@ -34183,7 +34461,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
34183
34461
|
];
|
|
34184
34462
|
|
|
34185
34463
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
34186
|
-
import { and as
|
|
34464
|
+
import { and as and28, eq as eq36, gte as gte6, ne as ne4, sql as sql13 } from "drizzle-orm";
|
|
34187
34465
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
34188
34466
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
34189
34467
|
function skippedNoProject5() {
|
|
@@ -34197,8 +34475,8 @@ function skippedNoProject5() {
|
|
|
34197
34475
|
function loadProbes(ctx) {
|
|
34198
34476
|
if (!ctx.project) return [];
|
|
34199
34477
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
34200
|
-
|
|
34201
|
-
|
|
34478
|
+
and28(
|
|
34479
|
+
eq36(trafficSources.projectId, ctx.project.id),
|
|
34202
34480
|
ne4(trafficSources.status, TrafficSourceStatuses.archived)
|
|
34203
34481
|
)
|
|
34204
34482
|
).all();
|
|
@@ -34278,17 +34556,17 @@ var recentDataCheck = {
|
|
|
34278
34556
|
const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
|
|
34279
34557
|
const recentCrawlers = Number(
|
|
34280
34558
|
ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
34281
|
-
|
|
34282
|
-
|
|
34283
|
-
|
|
34559
|
+
and28(
|
|
34560
|
+
eq36(crawlerEventsHourly.projectId, ctx.project.id),
|
|
34561
|
+
gte6(crawlerEventsHourly.tsHour, warnCutoff)
|
|
34284
34562
|
)
|
|
34285
34563
|
).get()?.total ?? 0
|
|
34286
34564
|
);
|
|
34287
34565
|
const recentReferrals = Number(
|
|
34288
34566
|
ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
34289
|
-
|
|
34290
|
-
|
|
34291
|
-
|
|
34567
|
+
and28(
|
|
34568
|
+
eq36(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
34569
|
+
gte6(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
34292
34570
|
)
|
|
34293
34571
|
).get()?.total ?? 0
|
|
34294
34572
|
);
|
|
@@ -34302,17 +34580,17 @@ var recentDataCheck = {
|
|
|
34302
34580
|
}
|
|
34303
34581
|
const olderCrawlers = Number(
|
|
34304
34582
|
ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
34305
|
-
|
|
34306
|
-
|
|
34307
|
-
|
|
34583
|
+
and28(
|
|
34584
|
+
eq36(crawlerEventsHourly.projectId, ctx.project.id),
|
|
34585
|
+
gte6(crawlerEventsHourly.tsHour, failCutoff)
|
|
34308
34586
|
)
|
|
34309
34587
|
).get()?.total ?? 0
|
|
34310
34588
|
);
|
|
34311
34589
|
const olderReferrals = Number(
|
|
34312
34590
|
ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
34313
|
-
|
|
34314
|
-
|
|
34315
|
-
|
|
34591
|
+
and28(
|
|
34592
|
+
eq36(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
34593
|
+
gte6(aiReferralEventsHourly.tsHour, failCutoff)
|
|
34316
34594
|
)
|
|
34317
34595
|
).get()?.total ?? 0
|
|
34318
34596
|
);
|
|
@@ -34727,7 +35005,7 @@ async function doctorRoutes(app, opts) {
|
|
|
34727
35005
|
|
|
34728
35006
|
// ../api-routes/src/discovery/routes.ts
|
|
34729
35007
|
import crypto28 from "crypto";
|
|
34730
|
-
import { and as
|
|
35008
|
+
import { and as and29, desc as desc16, eq as eq37, gte as gte7, inArray as inArray13, isNull, or as or5 } from "drizzle-orm";
|
|
34731
35009
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
34732
35010
|
async function discoveryRoutes(app, opts) {
|
|
34733
35011
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -34761,13 +35039,13 @@ async function discoveryRoutes(app, opts) {
|
|
|
34761
35039
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
34762
35040
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
34763
35041
|
const decision = app.db.transaction((tx) => {
|
|
34764
|
-
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(
|
|
34765
|
-
|
|
34766
|
-
|
|
35042
|
+
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and29(
|
|
35043
|
+
eq37(discoverySessions.projectId, project.id),
|
|
35044
|
+
eq37(discoverySessions.icpDescription, icpDescription),
|
|
34767
35045
|
// Buyer is part of session identity: it changes the seed prompt's
|
|
34768
35046
|
// semantics, so a request with a different (or no) buyer must start
|
|
34769
35047
|
// its own session, never adopt another buyer's probes.
|
|
34770
|
-
parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) :
|
|
35048
|
+
parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq37(discoverySessions.buyerDescription, parsed.data.buyerDescription),
|
|
34771
35049
|
// Locations are identity too: a different service-area subset seeds
|
|
34772
35050
|
// and probes a different geo, so it must never reuse another geo's
|
|
34773
35051
|
// session. resolveLocations is deterministic (project-config order),
|
|
@@ -34777,17 +35055,17 @@ async function discoveryRoutes(app, opts) {
|
|
|
34777
35055
|
// locations a NULL row's subset is unknowable, so it conservatively
|
|
34778
35056
|
// never reuses — a one-time, bounded (2h window) non-reuse after
|
|
34779
35057
|
// upgrade, never a wrong reuse.
|
|
34780
|
-
locations.length === 0 ? or5(isNull(discoverySessions.locations),
|
|
35058
|
+
locations.length === 0 ? or5(isNull(discoverySessions.locations), eq37(discoverySessions.locations, locations)) : eq37(discoverySessions.locations, locations),
|
|
34781
35059
|
// Seed provider set is identity: a different phrasing distribution
|
|
34782
35060
|
// must never reuse another set's session. Null (= Gemini-only
|
|
34783
35061
|
// default, including explicit ['gemini']) matches legacy rows.
|
|
34784
|
-
seedProviders == null ? isNull(discoverySessions.seedProviders) :
|
|
35062
|
+
seedProviders == null ? isNull(discoverySessions.seedProviders) : eq37(discoverySessions.seedProviders, seedProviders),
|
|
34785
35063
|
inArray13(discoverySessions.status, [
|
|
34786
35064
|
DiscoverySessionStatuses.queued,
|
|
34787
35065
|
DiscoverySessionStatuses.seeding,
|
|
34788
35066
|
DiscoverySessionStatuses.probing
|
|
34789
35067
|
]),
|
|
34790
|
-
|
|
35068
|
+
gte7(discoverySessions.createdAt, ageFloorIso)
|
|
34791
35069
|
)).orderBy(desc16(discoverySessions.createdAt)).get();
|
|
34792
35070
|
if (existing && existing.runId) {
|
|
34793
35071
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
@@ -34857,7 +35135,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
34857
35135
|
const project = resolveProject(app.db, request.params.name);
|
|
34858
35136
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
34859
35137
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
34860
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
35138
|
+
const rows = app.db.select().from(discoverySessions).where(eq37(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
|
|
34861
35139
|
return reply.send(rows.map(serializeSession));
|
|
34862
35140
|
}
|
|
34863
35141
|
);
|
|
@@ -34865,11 +35143,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
34865
35143
|
"/projects/:name/discover/sessions/:id",
|
|
34866
35144
|
async (request, reply) => {
|
|
34867
35145
|
const project = resolveProject(app.db, request.params.name);
|
|
34868
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
35146
|
+
const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
|
|
34869
35147
|
if (!session || session.projectId !== project.id) {
|
|
34870
35148
|
throw notFound("Discovery session", request.params.id);
|
|
34871
35149
|
}
|
|
34872
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
35150
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
|
|
34873
35151
|
const detail = {
|
|
34874
35152
|
...serializeSession(session),
|
|
34875
35153
|
probes: probeRows.map(serializeProbe)
|
|
@@ -34881,7 +35159,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
34881
35159
|
"/projects/:name/discover/sessions/:id/harvest",
|
|
34882
35160
|
async (request, reply) => {
|
|
34883
35161
|
const project = resolveProject(app.db, request.params.name);
|
|
34884
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
35162
|
+
const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
|
|
34885
35163
|
if (!session || session.projectId !== project.id) {
|
|
34886
35164
|
throw notFound("Discovery session", request.params.id);
|
|
34887
35165
|
}
|
|
@@ -34889,7 +35167,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
34889
35167
|
const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
|
|
34890
35168
|
const applyAnchor = request.query.anchor !== "false";
|
|
34891
35169
|
const provider = session.seedProvider ?? "gemini";
|
|
34892
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
35170
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
|
|
34893
35171
|
const extract = opts.harvestSearchQueries;
|
|
34894
35172
|
const probesWithQueries = probeRows.map((row) => {
|
|
34895
35173
|
if (!extract || !row.rawResponse) return { searchQueries: [] };
|
|
@@ -34900,7 +35178,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
34900
35178
|
return { searchQueries: [] };
|
|
34901
35179
|
}
|
|
34902
35180
|
});
|
|
34903
|
-
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(
|
|
35181
|
+
const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq37(queries.projectId, project.id)).all().map((r) => r.query);
|
|
34904
35182
|
const anchorTerms = buildHarvestAnchorTerms(
|
|
34905
35183
|
[session.icpDescription ?? "", ...trackedQueries],
|
|
34906
35184
|
effectiveDomains(project)
|
|
@@ -34947,12 +35225,12 @@ async function discoveryRoutes(app, opts) {
|
|
|
34947
35225
|
"/projects/:name/discover/sessions/:id/promote",
|
|
34948
35226
|
async (request, reply) => {
|
|
34949
35227
|
const project = resolveProject(app.db, request.params.name);
|
|
34950
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
35228
|
+
const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
|
|
34951
35229
|
if (!session || session.projectId !== project.id) {
|
|
34952
35230
|
throw notFound("Discovery session", request.params.id);
|
|
34953
35231
|
}
|
|
34954
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
34955
|
-
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
35232
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
|
|
35233
|
+
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
|
|
34956
35234
|
const seenCompetitors = new Set(existingCompetitors);
|
|
34957
35235
|
const cited = /* @__PURE__ */ new Set();
|
|
34958
35236
|
const aspirational = /* @__PURE__ */ new Set();
|
|
@@ -34981,7 +35259,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
34981
35259
|
);
|
|
34982
35260
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
34983
35261
|
const project = resolveProject(app.db, request.params.name);
|
|
34984
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
35262
|
+
const session = app.db.select().from(discoverySessions).where(eq37(discoverySessions.id, request.params.id)).get();
|
|
34985
35263
|
if (!session || session.projectId !== project.id) {
|
|
34986
35264
|
throw notFound("Discovery session", request.params.id);
|
|
34987
35265
|
}
|
|
@@ -35004,7 +35282,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
35004
35282
|
const bucketSet = new Set(buckets);
|
|
35005
35283
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
35006
35284
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
35007
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
35285
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq37(discoveryProbes.sessionId, session.id)).all();
|
|
35008
35286
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
35009
35287
|
for (const probe of probeRows) {
|
|
35010
35288
|
if (!probe.bucket) continue;
|
|
@@ -35012,7 +35290,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
35012
35290
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
35013
35291
|
}
|
|
35014
35292
|
const existingQueries = new Set(
|
|
35015
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
35293
|
+
app.db.select({ query: queries.query }).from(queries).where(eq37(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
35016
35294
|
);
|
|
35017
35295
|
const promotedQueries = [];
|
|
35018
35296
|
const skippedQueries = [];
|
|
@@ -35028,7 +35306,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
35028
35306
|
const skippedCompetitors = [];
|
|
35029
35307
|
if (includeCompetitors) {
|
|
35030
35308
|
const existingCompetitors = new Set(
|
|
35031
|
-
app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
35309
|
+
app.db.select({ domain: competitors.domain }).from(competitors).where(eq37(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
35032
35310
|
);
|
|
35033
35311
|
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
35034
35312
|
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
@@ -35147,7 +35425,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
|
35147
35425
|
|
|
35148
35426
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
35149
35427
|
import crypto29 from "crypto";
|
|
35150
|
-
import { eq as
|
|
35428
|
+
import { eq as eq38 } from "drizzle-orm";
|
|
35151
35429
|
var DEFAULT_MAX_PROBES = 100;
|
|
35152
35430
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
35153
35431
|
function classifyProbeBucket(input) {
|
|
@@ -35234,7 +35512,7 @@ async function executeDiscovery(opts) {
|
|
|
35234
35512
|
status: DiscoverySessionStatuses.seeding,
|
|
35235
35513
|
dedupThreshold,
|
|
35236
35514
|
startedAt
|
|
35237
|
-
}).where(
|
|
35515
|
+
}).where(eq38(discoverySessions.id, opts.sessionId)).run();
|
|
35238
35516
|
const seedResult = await opts.deps.seed({
|
|
35239
35517
|
project: opts.project,
|
|
35240
35518
|
icpDescription: opts.icpDescription,
|
|
@@ -35295,7 +35573,7 @@ async function executeDiscovery(opts) {
|
|
|
35295
35573
|
dedupBandPairFraction: dedupStats.bandPairFraction,
|
|
35296
35574
|
dedupPairsTotal: dedupStats.pairsTotal,
|
|
35297
35575
|
warning
|
|
35298
|
-
}).where(
|
|
35576
|
+
}).where(eq38(discoverySessions.id, opts.sessionId)).run();
|
|
35299
35577
|
const probeLocation = opts.locations?.[0];
|
|
35300
35578
|
const probeResults = await mapWithConcurrency(
|
|
35301
35579
|
probedCanonicals,
|
|
@@ -35349,7 +35627,7 @@ async function executeDiscovery(opts) {
|
|
|
35349
35627
|
wastedCount: buckets["wasted-surface"],
|
|
35350
35628
|
competitorMap,
|
|
35351
35629
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35352
|
-
}).where(
|
|
35630
|
+
}).where(eq38(discoverySessions.id, opts.sessionId)).run();
|
|
35353
35631
|
upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
|
|
35354
35632
|
return {
|
|
35355
35633
|
buckets,
|
|
@@ -35389,7 +35667,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
35389
35667
|
status: DiscoverySessionStatuses.failed,
|
|
35390
35668
|
error,
|
|
35391
35669
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35392
|
-
}).where(
|
|
35670
|
+
}).where(eq38(discoverySessions.id, sessionId)).run();
|
|
35393
35671
|
}
|
|
35394
35672
|
function dedupeStrings(input) {
|
|
35395
35673
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -35407,7 +35685,7 @@ function dedupeStrings(input) {
|
|
|
35407
35685
|
|
|
35408
35686
|
// ../api-routes/src/technical-aeo.ts
|
|
35409
35687
|
import crypto30 from "crypto";
|
|
35410
|
-
import { and as
|
|
35688
|
+
import { and as and30, asc as asc6, count, desc as desc17, eq as eq39, inArray as inArray14 } from "drizzle-orm";
|
|
35411
35689
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
35412
35690
|
function emptyScore(projectName) {
|
|
35413
35691
|
return {
|
|
@@ -35439,9 +35717,9 @@ function parsePositiveInt(value, fallback, max) {
|
|
|
35439
35717
|
async function technicalAeoRoutes(app, opts) {
|
|
35440
35718
|
app.get("/projects/:name/technical-aeo", async (request) => {
|
|
35441
35719
|
const project = resolveProject(app.db, request.params.name);
|
|
35442
|
-
const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs,
|
|
35443
|
-
|
|
35444
|
-
|
|
35720
|
+
const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq39(siteAuditSnapshots.runId, runs.id)).where(and30(
|
|
35721
|
+
eq39(siteAuditSnapshots.projectId, project.id),
|
|
35722
|
+
eq39(runs.kind, RunKinds["site-audit"]),
|
|
35445
35723
|
inArray14(runs.status, SURFACEABLE_STATUSES),
|
|
35446
35724
|
notProbeRun()
|
|
35447
35725
|
)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(2).all();
|
|
@@ -35474,9 +35752,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
35474
35752
|
});
|
|
35475
35753
|
app.get("/projects/:name/technical-aeo/pages", async (request) => {
|
|
35476
35754
|
const project = resolveProject(app.db, request.params.name);
|
|
35477
|
-
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs,
|
|
35478
|
-
|
|
35479
|
-
|
|
35755
|
+
const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq39(siteAuditSnapshots.runId, runs.id)).where(and30(
|
|
35756
|
+
eq39(siteAuditSnapshots.projectId, project.id),
|
|
35757
|
+
eq39(runs.kind, RunKinds["site-audit"]),
|
|
35480
35758
|
inArray14(runs.status, SURFACEABLE_STATUSES),
|
|
35481
35759
|
notProbeRun()
|
|
35482
35760
|
)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
@@ -35484,14 +35762,14 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
35484
35762
|
return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
|
|
35485
35763
|
}
|
|
35486
35764
|
const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
|
|
35487
|
-
const conds = [
|
|
35488
|
-
if (statusFilter) conds.push(
|
|
35489
|
-
const where =
|
|
35765
|
+
const conds = [eq39(siteAuditPages.runId, latest.runId)];
|
|
35766
|
+
if (statusFilter) conds.push(eq39(siteAuditPages.status, statusFilter));
|
|
35767
|
+
const where = and30(...conds);
|
|
35490
35768
|
const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
|
|
35491
35769
|
const total = totalRow?.value ?? 0;
|
|
35492
35770
|
const limit = parsePositiveInt(request.query.limit, 100, 500);
|
|
35493
35771
|
const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
|
|
35494
|
-
const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ?
|
|
35772
|
+
const orderBy = request.query.sort === "score-desc" ? desc17(siteAuditPages.overallScore) : request.query.sort === "url" ? asc6(siteAuditPages.url) : asc6(siteAuditPages.overallScore);
|
|
35495
35773
|
const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
|
|
35496
35774
|
const pages = rows.map((row) => ({
|
|
35497
35775
|
url: row.url,
|
|
@@ -35510,9 +35788,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
35510
35788
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
35511
35789
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
35512
35790
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
35513
|
-
}).from(siteAuditSnapshots).innerJoin(runs,
|
|
35514
|
-
|
|
35515
|
-
|
|
35791
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq39(siteAuditSnapshots.runId, runs.id)).where(and30(
|
|
35792
|
+
eq39(siteAuditSnapshots.projectId, project.id),
|
|
35793
|
+
eq39(runs.kind, RunKinds["site-audit"]),
|
|
35516
35794
|
inArray14(runs.status, SURFACEABLE_STATUSES),
|
|
35517
35795
|
notProbeRun()
|
|
35518
35796
|
)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
@@ -35524,9 +35802,9 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
35524
35802
|
if (!parsed.success) {
|
|
35525
35803
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
|
|
35526
35804
|
}
|
|
35527
|
-
const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(
|
|
35528
|
-
|
|
35529
|
-
|
|
35805
|
+
const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and30(
|
|
35806
|
+
eq39(runs.projectId, project.id),
|
|
35807
|
+
eq39(runs.kind, RunKinds["site-audit"]),
|
|
35530
35808
|
inArray14(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
35531
35809
|
)).get();
|
|
35532
35810
|
if (existing) {
|
|
@@ -35632,6 +35910,7 @@ async function apiRoutes(app, opts) {
|
|
|
35632
35910
|
await api.register(reportRoutes);
|
|
35633
35911
|
await api.register(citationRoutes);
|
|
35634
35912
|
await api.register(visibilityStatsRoutes);
|
|
35913
|
+
await api.register(resultsExportRoutes);
|
|
35635
35914
|
await api.register(compositeRoutes);
|
|
35636
35915
|
await api.register(contentRoutes, {
|
|
35637
35916
|
explainContentRecommendation: opts.explainContentRecommendation,
|
|
@@ -36118,9 +36397,9 @@ var IntelligenceService = class {
|
|
|
36118
36397
|
*/
|
|
36119
36398
|
analyzeAndPersist(runId, projectId) {
|
|
36120
36399
|
const recentRuns = this.db.select().from(runs).where(
|
|
36121
|
-
|
|
36122
|
-
|
|
36123
|
-
or6(
|
|
36400
|
+
and31(
|
|
36401
|
+
eq40(runs.projectId, projectId),
|
|
36402
|
+
or6(eq40(runs.status, "completed"), eq40(runs.status, "partial")),
|
|
36124
36403
|
// Defensive: RunCoordinator already skips probes before this is
|
|
36125
36404
|
// called, but if a future call site invokes analyzeAndPersist
|
|
36126
36405
|
// directly for a probe, probes still must not pollute the
|
|
@@ -36202,7 +36481,7 @@ var IntelligenceService = class {
|
|
|
36202
36481
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
36203
36482
|
*/
|
|
36204
36483
|
analyzeAndPersistGbp(runId, projectId) {
|
|
36205
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
36484
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq40(runs.id, runId)).get();
|
|
36206
36485
|
if (!runRow) {
|
|
36207
36486
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
36208
36487
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -36210,11 +36489,11 @@ var IntelligenceService = class {
|
|
|
36210
36489
|
}
|
|
36211
36490
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
36212
36491
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
36213
|
-
const selected = this.db.select().from(gbpLocations).where(
|
|
36214
|
-
|
|
36215
|
-
|
|
36216
|
-
|
|
36217
|
-
|
|
36492
|
+
const selected = this.db.select().from(gbpLocations).where(and31(
|
|
36493
|
+
eq40(gbpLocations.projectId, projectId),
|
|
36494
|
+
eq40(gbpLocations.selected, true),
|
|
36495
|
+
gte8(gbpLocations.syncedAt, windowStart),
|
|
36496
|
+
lte5(gbpLocations.syncedAt, windowEnd)
|
|
36218
36497
|
)).all();
|
|
36219
36498
|
if (selected.length === 0) {
|
|
36220
36499
|
log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });
|
|
@@ -36247,12 +36526,12 @@ var IntelligenceService = class {
|
|
|
36247
36526
|
}
|
|
36248
36527
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
36249
36528
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
36250
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(
|
|
36251
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(
|
|
36252
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(
|
|
36253
|
-
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(
|
|
36529
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and31(eq40(gbpDailyMetrics.projectId, projectId), eq40(gbpDailyMetrics.locationName, locationName))).all();
|
|
36530
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and31(eq40(gbpPlaceActions.projectId, projectId), eq40(gbpPlaceActions.locationName, locationName))).all();
|
|
36531
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and31(eq40(gbpLodgingSnapshots.projectId, projectId), eq40(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
36532
|
+
const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and31(eq40(gbpLocations.projectId, projectId), eq40(gbpLocations.locationName, locationName))).get();
|
|
36254
36533
|
const descriptionMissing = !(ownerRow?.description ?? "").trim();
|
|
36255
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(
|
|
36534
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and31(eq40(gbpPlaceDetails.projectId, projectId), eq40(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
36256
36535
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
36257
36536
|
const summary = buildGbpSummary({
|
|
36258
36537
|
locationName,
|
|
@@ -36288,7 +36567,7 @@ var IntelligenceService = class {
|
|
|
36288
36567
|
/** Build the month-over-month keyword series for a location from the
|
|
36289
36568
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
36290
36569
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
36291
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(
|
|
36570
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and31(eq40(gbpKeywordMonthly.projectId, projectId), eq40(gbpKeywordMonthly.locationName, locationName))).all();
|
|
36292
36571
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
36293
36572
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
36294
36573
|
const recentMonth = months[0] ?? null;
|
|
@@ -36319,7 +36598,7 @@ var IntelligenceService = class {
|
|
|
36319
36598
|
*/
|
|
36320
36599
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
36321
36600
|
const covered = new Set(coveredLocationNames);
|
|
36322
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(
|
|
36601
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and31(eq40(insights.projectId, projectId), eq40(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
36323
36602
|
const staleIds = [];
|
|
36324
36603
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
36325
36604
|
for (const row of existing) {
|
|
@@ -36330,7 +36609,7 @@ var IntelligenceService = class {
|
|
|
36330
36609
|
}
|
|
36331
36610
|
this.db.transaction((tx) => {
|
|
36332
36611
|
for (const id of staleIds) {
|
|
36333
|
-
tx.delete(insights).where(
|
|
36612
|
+
tx.delete(insights).where(eq40(insights.id, id)).run();
|
|
36334
36613
|
}
|
|
36335
36614
|
for (const insight of gbpInsights) {
|
|
36336
36615
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -36408,7 +36687,7 @@ var IntelligenceService = class {
|
|
|
36408
36687
|
* create per run + aggregate). DB is left untouched.
|
|
36409
36688
|
*/
|
|
36410
36689
|
backfill(projectName, opts, onProgress) {
|
|
36411
|
-
const project = this.db.select().from(projects).where(
|
|
36690
|
+
const project = this.db.select().from(projects).where(eq40(projects.name, projectName)).get();
|
|
36412
36691
|
if (!project) {
|
|
36413
36692
|
throw new Error(`Project "${projectName}" not found`);
|
|
36414
36693
|
}
|
|
@@ -36421,13 +36700,13 @@ var IntelligenceService = class {
|
|
|
36421
36700
|
sinceTimestamp = parsed;
|
|
36422
36701
|
}
|
|
36423
36702
|
const allRuns = this.db.select().from(runs).where(
|
|
36424
|
-
|
|
36425
|
-
|
|
36426
|
-
or6(
|
|
36703
|
+
and31(
|
|
36704
|
+
eq40(runs.projectId, project.id),
|
|
36705
|
+
or6(eq40(runs.status, "completed"), eq40(runs.status, "partial")),
|
|
36427
36706
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
36428
36707
|
ne5(runs.trigger, RunTriggers.probe)
|
|
36429
36708
|
)
|
|
36430
|
-
).orderBy(
|
|
36709
|
+
).orderBy(asc7(runs.finishedAt)).all();
|
|
36431
36710
|
let startIdx = 0;
|
|
36432
36711
|
let endIdx = allRuns.length;
|
|
36433
36712
|
if (opts?.fromRunId) {
|
|
@@ -36502,7 +36781,7 @@ var IntelligenceService = class {
|
|
|
36502
36781
|
return { processed, skipped, totalInsights };
|
|
36503
36782
|
}
|
|
36504
36783
|
loadTrackedCompetitors(projectId) {
|
|
36505
|
-
return this.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
36784
|
+
return this.db.select({ domain: competitors.domain }).from(competitors).where(eq40(competitors.projectId, projectId)).all().map((r) => r.domain);
|
|
36506
36785
|
}
|
|
36507
36786
|
/**
|
|
36508
36787
|
* Wipe transition signals from an analysis result while keeping health.
|
|
@@ -36523,15 +36802,15 @@ var IntelligenceService = class {
|
|
|
36523
36802
|
}
|
|
36524
36803
|
persistResult(result, runId, projectId) {
|
|
36525
36804
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
36526
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
36805
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq40(insights.runId, runId)).all();
|
|
36527
36806
|
for (const row of existingInsights) {
|
|
36528
36807
|
if (row.dismissed) {
|
|
36529
36808
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
36530
36809
|
}
|
|
36531
36810
|
}
|
|
36532
36811
|
this.db.transaction((tx) => {
|
|
36533
|
-
tx.delete(insights).where(
|
|
36534
|
-
tx.delete(healthSnapshots).where(
|
|
36812
|
+
tx.delete(insights).where(eq40(insights.runId, runId)).run();
|
|
36813
|
+
tx.delete(healthSnapshots).where(eq40(healthSnapshots.runId, runId)).run();
|
|
36535
36814
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36536
36815
|
for (const insight of result.insights) {
|
|
36537
36816
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -36584,24 +36863,24 @@ var IntelligenceService = class {
|
|
|
36584
36863
|
applySeverityTiering(rawInsights, excludeRunId, projectId) {
|
|
36585
36864
|
const regressions = rawInsights.filter((i) => i.type === "regression");
|
|
36586
36865
|
if (regressions.length === 0) return rawInsights;
|
|
36587
|
-
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(
|
|
36866
|
+
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq40(gscSearchData.projectId, projectId)).all();
|
|
36588
36867
|
const gscConnected = gscRows.length > 0;
|
|
36589
36868
|
const gscImpressionsByQuery = /* @__PURE__ */ new Map();
|
|
36590
36869
|
for (const row of gscRows) {
|
|
36591
36870
|
const key = row.query.toLowerCase();
|
|
36592
36871
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
36593
36872
|
}
|
|
36594
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
36873
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq40(projects.id, projectId)).get();
|
|
36595
36874
|
const locationCount = Math.max(
|
|
36596
36875
|
1,
|
|
36597
36876
|
(projectRow?.locations ?? []).length
|
|
36598
36877
|
);
|
|
36599
36878
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
36600
36879
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
36601
|
-
|
|
36602
|
-
|
|
36603
|
-
|
|
36604
|
-
or6(
|
|
36880
|
+
and31(
|
|
36881
|
+
eq40(runs.projectId, projectId),
|
|
36882
|
+
eq40(runs.kind, RunKinds["answer-visibility"]),
|
|
36883
|
+
or6(eq40(runs.status, "completed"), eq40(runs.status, "partial")),
|
|
36605
36884
|
// Defensive — see top of file.
|
|
36606
36885
|
ne5(runs.trigger, RunTriggers.probe)
|
|
36607
36886
|
)
|
|
@@ -36621,7 +36900,7 @@ var IntelligenceService = class {
|
|
|
36621
36900
|
const haveHistory = recentRunIds.length > 0;
|
|
36622
36901
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
36623
36902
|
if (haveHistory) {
|
|
36624
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(
|
|
36903
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and31(eq40(insights.type, "regression"), inArray15(insights.runId, recentRunIds))).all();
|
|
36625
36904
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
36626
36905
|
for (const row of priorRows) {
|
|
36627
36906
|
if (!row.runId) continue;
|
|
@@ -36650,7 +36929,7 @@ var IntelligenceService = class {
|
|
|
36650
36929
|
});
|
|
36651
36930
|
}
|
|
36652
36931
|
buildRunData(runId, projectId, completedAt, location = null) {
|
|
36653
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
36932
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq40(projects.id, projectId)).get();
|
|
36654
36933
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
36655
36934
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
36656
36935
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -36667,7 +36946,7 @@ var IntelligenceService = class {
|
|
|
36667
36946
|
citedDomains: querySnapshots.citedDomains,
|
|
36668
36947
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
36669
36948
|
snapshotLocation: querySnapshots.location
|
|
36670
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
36949
|
+
}).from(querySnapshots).leftJoin(queries, eq40(querySnapshots.queryId, queries.id)).where(eq40(querySnapshots.runId, runId)).all();
|
|
36671
36950
|
const snapshots = [];
|
|
36672
36951
|
let orphanCount = 0;
|
|
36673
36952
|
for (const r of rows) {
|