@ainyc/canonry 4.68.1 → 4.69.1
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 +20 -1
- package/assets/assets/{BacklinksPage-B9Q2r9zM.js → BacklinksPage-DLsgGSXv.js} +1 -1
- package/assets/assets/{ChartPrimitives-D-YWOWK-.js → ChartPrimitives-CERLI8Z-.js} +1 -1
- package/assets/assets/{ProjectPage-BXgxWKyT.js → ProjectPage-CaRqQ1vH.js} +1 -1
- package/assets/assets/{RunRow-CJpjUaht.js → RunRow-C3x47chX.js} +1 -1
- package/assets/assets/{RunsPage-DVl_pgqe.js → RunsPage-DRIlEete.js} +1 -1
- package/assets/assets/{SettingsPage-CAiVL2mo.js → SettingsPage-CHaqBpnQ.js} +1 -1
- package/assets/assets/{TrafficPage-B8dnHFqk.js → TrafficPage-UbL0daLy.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-37s8p8eZ.js → TrafficSourceDetailPage-CjLDbjf_.js} +1 -1
- package/assets/assets/{extract-error-message-DwqbPRoa.js → extract-error-message-BndXGmUh.js} +1 -1
- package/assets/assets/{index-DFo2OL9c.js → index-BCwC5OlW.js} +39 -39
- package/assets/assets/{index-BquJzH0t.css → index-BUNCrWTe.css} +1 -1
- package/assets/assets/{server-traffic-CMFP8-x2.js → server-traffic-DOnVZFEw.js} +1 -1
- package/assets/assets/{trash-2-C4sYVIa6.js → trash-2-HEZdy4sJ.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-D75O5A27.js → chunk-5FM7QRYD.js} +1558 -1526
- package/dist/{chunk-GZYLAE6M.js → chunk-SBYA3LEJ.js} +4 -4
- package/dist/{chunk-Y24OE7R3.js → chunk-WFM2O72W.js} +494 -309
- package/dist/{chunk-YYFBMDLC.js → chunk-ZNWMVYYU.js} +53 -1
- package/dist/cli.js +197 -80
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-5V2JWQ6K.js → intelligence-service-DVVSE3G7.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +7 -7
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
absolutizeProjectUrl,
|
|
33
33
|
actionConfidenceLabel,
|
|
34
34
|
agentProvidersResponseDtoSchema,
|
|
35
|
+
apiKeyDtoSchema,
|
|
36
|
+
apiKeyListDtoSchema,
|
|
35
37
|
auditLogEntrySchema,
|
|
36
38
|
authInvalid,
|
|
37
39
|
authRequired,
|
|
@@ -74,6 +76,8 @@ import {
|
|
|
74
76
|
contentTargetDismissalDtoSchema,
|
|
75
77
|
contentTargetDismissalsResponseDtoSchema,
|
|
76
78
|
contentTargetsResponseDtoSchema,
|
|
79
|
+
createApiKeyRequestSchema,
|
|
80
|
+
createdApiKeyDtoSchema,
|
|
77
81
|
dedupeReportActions,
|
|
78
82
|
dedupeReportOpportunities,
|
|
79
83
|
deliveryFailed,
|
|
@@ -208,10 +212,10 @@ import {
|
|
|
208
212
|
wordpressSchemaDeployResultDtoSchema,
|
|
209
213
|
wordpressSchemaStatusResultDtoSchema,
|
|
210
214
|
wordpressStatusDtoSchema
|
|
211
|
-
} from "./chunk-
|
|
215
|
+
} from "./chunk-5FM7QRYD.js";
|
|
212
216
|
|
|
213
217
|
// src/intelligence-service.ts
|
|
214
|
-
import { eq as
|
|
218
|
+
import { eq as eq31, desc as desc16, asc as asc3, and as and23, ne as ne5, or as or5, inArray as inArray11, gte as gte6, lte as lte3 } from "drizzle-orm";
|
|
215
219
|
|
|
216
220
|
// ../db/src/client.ts
|
|
217
221
|
import { mkdirSync } from "fs";
|
|
@@ -4326,19 +4330,26 @@ function buildRunHistory(runs2, snapshotsByRunId, limit = DEFAULT_RUN_HISTORY_LI
|
|
|
4326
4330
|
return recent.map((run) => {
|
|
4327
4331
|
const snapshots = snapshotsByRunId.get(run.id) ?? [];
|
|
4328
4332
|
const queryCited = /* @__PURE__ */ new Map();
|
|
4333
|
+
const queryMentioned = /* @__PURE__ */ new Map();
|
|
4329
4334
|
for (const snap of snapshots) {
|
|
4330
4335
|
if (!queryCited.has(snap.queryId)) queryCited.set(snap.queryId, false);
|
|
4331
4336
|
if (snap.citationState === CitationStates.cited) queryCited.set(snap.queryId, true);
|
|
4337
|
+
if (!queryMentioned.has(snap.queryId)) queryMentioned.set(snap.queryId, false);
|
|
4338
|
+
if (snap.answerMentioned === true) queryMentioned.set(snap.queryId, true);
|
|
4332
4339
|
}
|
|
4333
4340
|
const totalCount = queryCited.size;
|
|
4334
4341
|
const citedCount = [...queryCited.values()].filter(Boolean).length;
|
|
4335
4342
|
const citationRate = totalCount > 0 ? Math.round(citedCount / totalCount * 100) : 0;
|
|
4343
|
+
const mentionedCount = [...queryMentioned.values()].filter(Boolean).length;
|
|
4344
|
+
const mentionRate = totalCount > 0 ? Math.round(mentionedCount / totalCount * 100) : 0;
|
|
4336
4345
|
return {
|
|
4337
4346
|
runId: run.id,
|
|
4338
4347
|
createdAt: run.createdAt,
|
|
4339
4348
|
citedCount,
|
|
4340
4349
|
totalCount,
|
|
4341
4350
|
citationRate,
|
|
4351
|
+
mentionedCount,
|
|
4352
|
+
mentionRate,
|
|
4342
4353
|
status: run.status
|
|
4343
4354
|
};
|
|
4344
4355
|
});
|
|
@@ -4831,7 +4842,7 @@ function requireScope(request, scope) {
|
|
|
4831
4842
|
if (key.scopes.includes("*") || key.scopes.includes(scope)) return;
|
|
4832
4843
|
throw forbidden(`This action requires the "${scope}" scope on your API key.`);
|
|
4833
4844
|
}
|
|
4834
|
-
function
|
|
4845
|
+
function hashApiKey(key) {
|
|
4835
4846
|
return crypto2.createHash("sha256").update(key).digest("hex");
|
|
4836
4847
|
}
|
|
4837
4848
|
var SKIP_PATHS = ["/health"];
|
|
@@ -4870,7 +4881,7 @@ async function authPlugin(app, opts = {}) {
|
|
|
4870
4881
|
throw authRequired();
|
|
4871
4882
|
}
|
|
4872
4883
|
const token = parts[1];
|
|
4873
|
-
const hash =
|
|
4884
|
+
const hash = hashApiKey(token);
|
|
4874
4885
|
key = app.db.select().from(apiKeys).where(eq(apiKeys.keyHash, hash)).get();
|
|
4875
4886
|
if (!key || key.revokedAt) {
|
|
4876
4887
|
throw authInvalid();
|
|
@@ -12030,6 +12041,7 @@ async function compositeRoutes(app) {
|
|
|
12030
12041
|
const attentionItems = buildAttentionItems(insightRows, allRuns);
|
|
12031
12042
|
const sparklineRuns = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt, status: r.status }));
|
|
12032
12043
|
const runHistory = buildRunHistory(sparklineRuns, snapshotsByRun);
|
|
12044
|
+
scores.mention.trend = runHistory.map((p) => p.mentionRate);
|
|
12033
12045
|
scores.visibility.trend = runHistory.map((p) => p.citationRate);
|
|
12034
12046
|
const suggestedQueries = buildSuggestedQueriesFromGsc(
|
|
12035
12047
|
app,
|
|
@@ -12190,17 +12202,30 @@ function loadSnapshotsByRunIds(app, runIds) {
|
|
|
12190
12202
|
}
|
|
12191
12203
|
function summarizeFromSnapshots(snapshots) {
|
|
12192
12204
|
const empty = {
|
|
12193
|
-
queryCounts: {
|
|
12205
|
+
queryCounts: {
|
|
12206
|
+
totalQueries: 0,
|
|
12207
|
+
citedQueries: 0,
|
|
12208
|
+
notCitedQueries: 0,
|
|
12209
|
+
citedRate: 0,
|
|
12210
|
+
mentionedQueries: 0,
|
|
12211
|
+
notMentionedQueries: 0,
|
|
12212
|
+
mentionRate: 0
|
|
12213
|
+
},
|
|
12194
12214
|
providers: []
|
|
12195
12215
|
};
|
|
12196
12216
|
if (snapshots.length === 0) return empty;
|
|
12197
12217
|
const perQuery = /* @__PURE__ */ new Map();
|
|
12218
|
+
const perQueryMentioned = /* @__PURE__ */ new Map();
|
|
12198
12219
|
const perProvider = /* @__PURE__ */ new Map();
|
|
12199
12220
|
for (const snap of snapshots) {
|
|
12200
12221
|
const cited = snap.citationState === CitationStates.cited;
|
|
12201
12222
|
if (!perQuery.has(snap.queryId) || cited) {
|
|
12202
12223
|
perQuery.set(snap.queryId, cited);
|
|
12203
12224
|
}
|
|
12225
|
+
const mentioned = snap.answerMentioned === true;
|
|
12226
|
+
if (!perQueryMentioned.has(snap.queryId) || mentioned) {
|
|
12227
|
+
perQueryMentioned.set(snap.queryId, mentioned);
|
|
12228
|
+
}
|
|
12204
12229
|
const bucket = perProvider.get(snap.provider) ?? { cited: 0, total: 0 };
|
|
12205
12230
|
bucket.total += 1;
|
|
12206
12231
|
if (cited) bucket.cited += 1;
|
|
@@ -12213,6 +12238,12 @@ function summarizeFromSnapshots(snapshots) {
|
|
|
12213
12238
|
}
|
|
12214
12239
|
const notCitedQueries = totalQueries - citedQueries;
|
|
12215
12240
|
const citedRate = totalQueries === 0 ? 0 : Number((citedQueries / totalQueries).toFixed(4));
|
|
12241
|
+
let mentionedQueries = 0;
|
|
12242
|
+
for (const wasMentioned of perQueryMentioned.values()) {
|
|
12243
|
+
if (wasMentioned) mentionedQueries += 1;
|
|
12244
|
+
}
|
|
12245
|
+
const notMentionedQueries = totalQueries - mentionedQueries;
|
|
12246
|
+
const mentionRate = totalQueries === 0 ? 0 : Number((mentionedQueries / totalQueries).toFixed(4));
|
|
12216
12247
|
const providers = [...perProvider.entries()].map(([provider, { cited, total }]) => ({
|
|
12217
12248
|
provider,
|
|
12218
12249
|
cited,
|
|
@@ -12220,7 +12251,15 @@ function summarizeFromSnapshots(snapshots) {
|
|
|
12220
12251
|
citedRate: total === 0 ? 0 : Number((cited / total).toFixed(4))
|
|
12221
12252
|
})).sort((a, b) => a.provider.localeCompare(b.provider));
|
|
12222
12253
|
return {
|
|
12223
|
-
queryCounts: {
|
|
12254
|
+
queryCounts: {
|
|
12255
|
+
totalQueries,
|
|
12256
|
+
citedQueries,
|
|
12257
|
+
notCitedQueries,
|
|
12258
|
+
citedRate,
|
|
12259
|
+
mentionedQueries,
|
|
12260
|
+
notMentionedQueries,
|
|
12261
|
+
mentionRate
|
|
12262
|
+
},
|
|
12224
12263
|
providers
|
|
12225
12264
|
};
|
|
12226
12265
|
}
|
|
@@ -12548,6 +12587,8 @@ function makeSnippet(text2, query) {
|
|
|
12548
12587
|
import { z } from "zod";
|
|
12549
12588
|
var SCHEMA_TABLE = {
|
|
12550
12589
|
AgentProvidersResponseDto: agentProvidersResponseDtoSchema,
|
|
12590
|
+
ApiKeyDto: apiKeyDtoSchema,
|
|
12591
|
+
ApiKeyListDto: apiKeyListDtoSchema,
|
|
12551
12592
|
AuditLogEntry: auditLogEntrySchema,
|
|
12552
12593
|
BacklinkHistoryEntry: backlinkHistoryEntrySchema,
|
|
12553
12594
|
BacklinkListResponse: backlinkListResponseSchema,
|
|
@@ -12575,6 +12616,8 @@ var SCHEMA_TABLE = {
|
|
|
12575
12616
|
ContentTargetDismissalDto: contentTargetDismissalDtoSchema,
|
|
12576
12617
|
ContentTargetDismissalsResponseDto: contentTargetDismissalsResponseDtoSchema,
|
|
12577
12618
|
ContentTargetsResponseDto: contentTargetsResponseDtoSchema,
|
|
12619
|
+
CreateApiKeyRequest: createApiKeyRequestSchema,
|
|
12620
|
+
CreatedApiKeyDto: createdApiKeyDtoSchema,
|
|
12578
12621
|
RecommendationExplanationDto: recommendationExplanationDtoSchema,
|
|
12579
12622
|
DiscoveryPromotePreview: discoveryPromotePreviewSchema,
|
|
12580
12623
|
DiscoveryPromoteResult: discoveryPromoteResultSchema,
|
|
@@ -12742,6 +12785,13 @@ var notificationIdParameter = {
|
|
|
12742
12785
|
description: "Notification ID.",
|
|
12743
12786
|
schema: stringSchema
|
|
12744
12787
|
};
|
|
12788
|
+
var keyIdParameter = {
|
|
12789
|
+
name: "id",
|
|
12790
|
+
in: "path",
|
|
12791
|
+
required: true,
|
|
12792
|
+
description: "API key ID.",
|
|
12793
|
+
schema: stringSchema
|
|
12794
|
+
};
|
|
12745
12795
|
var providerNameParameter = {
|
|
12746
12796
|
name: "name",
|
|
12747
12797
|
in: "path",
|
|
@@ -13665,6 +13715,50 @@ var routeCatalog = [
|
|
|
13665
13715
|
501: errorResponse("Google settings updates are not supported.")
|
|
13666
13716
|
}
|
|
13667
13717
|
},
|
|
13718
|
+
{
|
|
13719
|
+
method: "get",
|
|
13720
|
+
path: "/api/v1/keys",
|
|
13721
|
+
summary: "List API keys",
|
|
13722
|
+
description: "Returns every API key on the instance, newest first, as SAFE metadata only \u2014 id, name, key prefix, scopes, created / last-used / revoked timestamps. The stored hash and the plaintext token are NEVER returned here; the raw token is shown exactly once at creation. Ungated: any valid bearer can list.",
|
|
13723
|
+
tags: ["keys"],
|
|
13724
|
+
responses: {
|
|
13725
|
+
200: jsonResponse("Keys returned.", "ApiKeyListDto")
|
|
13726
|
+
}
|
|
13727
|
+
},
|
|
13728
|
+
{
|
|
13729
|
+
method: "post",
|
|
13730
|
+
path: "/api/v1/keys",
|
|
13731
|
+
summary: "Create an API key",
|
|
13732
|
+
description: 'Mints a new `cnry_\u2026` API key. Requires the `keys.write` scope (the default `*` key satisfies it). The response includes the plaintext `key` field exactly ONCE \u2014 it is stored only as a sha256 hash and cannot be recovered later, so persist it on receipt. Omit `scopes` to default to `["*"]`.',
|
|
13733
|
+
tags: ["keys"],
|
|
13734
|
+
requestBody: {
|
|
13735
|
+
required: true,
|
|
13736
|
+
content: {
|
|
13737
|
+
"application/json": {
|
|
13738
|
+
schema: { $ref: "#/components/schemas/CreateApiKeyRequest" }
|
|
13739
|
+
}
|
|
13740
|
+
}
|
|
13741
|
+
},
|
|
13742
|
+
responses: {
|
|
13743
|
+
200: jsonResponse("Key created. Includes the one-time plaintext `key`.", "CreatedApiKeyDto"),
|
|
13744
|
+
400: errorResponse("Invalid request body."),
|
|
13745
|
+
403: errorResponse("Missing the keys.write scope.")
|
|
13746
|
+
}
|
|
13747
|
+
},
|
|
13748
|
+
{
|
|
13749
|
+
method: "post",
|
|
13750
|
+
path: "/api/v1/keys/{id}/revoke",
|
|
13751
|
+
summary: "Revoke an API key",
|
|
13752
|
+
description: "Revokes the key by id. Requires the `keys.write` scope. Revocation is immediate \u2014 the auth layer rejects a revoked key on the next request. Idempotent: revoking an already-revoked key returns it unchanged. Refuses to revoke the key the caller is currently authenticating with (use a different key).",
|
|
13753
|
+
tags: ["keys"],
|
|
13754
|
+
parameters: [keyIdParameter],
|
|
13755
|
+
responses: {
|
|
13756
|
+
200: jsonResponse("Key revoked (or already revoked).", "ApiKeyDto"),
|
|
13757
|
+
400: errorResponse("Cannot revoke the currently-authenticating key."),
|
|
13758
|
+
403: errorResponse("Missing the keys.write scope."),
|
|
13759
|
+
404: errorResponse("Key not found.")
|
|
13760
|
+
}
|
|
13761
|
+
},
|
|
13668
13762
|
{
|
|
13669
13763
|
method: "post",
|
|
13670
13764
|
path: "/api/v1/snapshot",
|
|
@@ -16514,6 +16608,96 @@ async function settingsRoutes(app, opts) {
|
|
|
16514
16608
|
});
|
|
16515
16609
|
}
|
|
16516
16610
|
|
|
16611
|
+
// ../api-routes/src/keys.ts
|
|
16612
|
+
import crypto12 from "crypto";
|
|
16613
|
+
import { desc as desc9, eq as eq17 } from "drizzle-orm";
|
|
16614
|
+
var KEYS_WRITE_SCOPE = "keys.write";
|
|
16615
|
+
function toApiKeyDto(row) {
|
|
16616
|
+
return {
|
|
16617
|
+
id: row.id,
|
|
16618
|
+
name: row.name,
|
|
16619
|
+
keyPrefix: row.keyPrefix,
|
|
16620
|
+
scopes: Array.isArray(row.scopes) ? row.scopes : [],
|
|
16621
|
+
createdAt: row.createdAt,
|
|
16622
|
+
lastUsedAt: row.lastUsedAt ?? null,
|
|
16623
|
+
revokedAt: row.revokedAt ?? null
|
|
16624
|
+
};
|
|
16625
|
+
}
|
|
16626
|
+
async function keysRoutes(app) {
|
|
16627
|
+
app.get("/keys", async () => {
|
|
16628
|
+
const rows = app.db.select().from(apiKeys).orderBy(desc9(apiKeys.createdAt)).all();
|
|
16629
|
+
return { keys: rows.map(toApiKeyDto) };
|
|
16630
|
+
});
|
|
16631
|
+
app.post("/keys", async (request) => {
|
|
16632
|
+
requireScope(request, KEYS_WRITE_SCOPE);
|
|
16633
|
+
const parsed = createApiKeyRequestSchema.safeParse(request.body);
|
|
16634
|
+
if (!parsed.success) {
|
|
16635
|
+
throw validationError("Invalid API key request", { issues: parsed.error.issues });
|
|
16636
|
+
}
|
|
16637
|
+
const { name, scopes } = parsed.data;
|
|
16638
|
+
const raw = `cnry_${crypto12.randomBytes(16).toString("hex")}`;
|
|
16639
|
+
const keyHash = hashApiKey(raw);
|
|
16640
|
+
const keyPrefix = raw.slice(0, 9);
|
|
16641
|
+
const id = crypto12.randomUUID();
|
|
16642
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16643
|
+
const effectiveScopes = scopes ?? ["*"];
|
|
16644
|
+
app.db.transaction((tx) => {
|
|
16645
|
+
tx.insert(apiKeys).values({
|
|
16646
|
+
id,
|
|
16647
|
+
name,
|
|
16648
|
+
keyHash,
|
|
16649
|
+
keyPrefix,
|
|
16650
|
+
scopes: effectiveScopes,
|
|
16651
|
+
createdAt: now
|
|
16652
|
+
}).run();
|
|
16653
|
+
writeAuditLog(tx, auditFromRequest(request, {
|
|
16654
|
+
actor: "api",
|
|
16655
|
+
action: "api-key.created",
|
|
16656
|
+
entityType: "api-key",
|
|
16657
|
+
entityId: id,
|
|
16658
|
+
diff: { name, keyPrefix, scopes: effectiveScopes }
|
|
16659
|
+
}));
|
|
16660
|
+
});
|
|
16661
|
+
const dto = {
|
|
16662
|
+
id,
|
|
16663
|
+
name,
|
|
16664
|
+
keyPrefix,
|
|
16665
|
+
scopes: effectiveScopes,
|
|
16666
|
+
createdAt: now,
|
|
16667
|
+
lastUsedAt: null,
|
|
16668
|
+
revokedAt: null,
|
|
16669
|
+
key: raw
|
|
16670
|
+
};
|
|
16671
|
+
return dto;
|
|
16672
|
+
});
|
|
16673
|
+
app.post("/keys/:id/revoke", async (request) => {
|
|
16674
|
+
requireScope(request, KEYS_WRITE_SCOPE);
|
|
16675
|
+
const { id } = request.params;
|
|
16676
|
+
const row = app.db.select().from(apiKeys).where(eq17(apiKeys.id, id)).get();
|
|
16677
|
+
if (!row) {
|
|
16678
|
+
throw notFound("API key", id);
|
|
16679
|
+
}
|
|
16680
|
+
if (request.apiKey?.id === id) {
|
|
16681
|
+
throw validationError("Cannot revoke the API key you are currently authenticating with");
|
|
16682
|
+
}
|
|
16683
|
+
if (row.revokedAt) {
|
|
16684
|
+
return toApiKeyDto(row);
|
|
16685
|
+
}
|
|
16686
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16687
|
+
app.db.transaction((tx) => {
|
|
16688
|
+
tx.update(apiKeys).set({ revokedAt: now }).where(eq17(apiKeys.id, id)).run();
|
|
16689
|
+
writeAuditLog(tx, auditFromRequest(request, {
|
|
16690
|
+
actor: "api",
|
|
16691
|
+
action: "api-key.revoked",
|
|
16692
|
+
entityType: "api-key",
|
|
16693
|
+
entityId: id,
|
|
16694
|
+
diff: { name: row.name, keyPrefix: row.keyPrefix }
|
|
16695
|
+
}));
|
|
16696
|
+
});
|
|
16697
|
+
return toApiKeyDto({ ...row, revokedAt: now });
|
|
16698
|
+
});
|
|
16699
|
+
}
|
|
16700
|
+
|
|
16517
16701
|
// ../api-routes/src/snapshot.ts
|
|
16518
16702
|
async function snapshotRoutes(app, opts) {
|
|
16519
16703
|
app.post("/snapshot", async (request) => {
|
|
@@ -16573,8 +16757,8 @@ async function telemetryRoutes(app, opts) {
|
|
|
16573
16757
|
}
|
|
16574
16758
|
|
|
16575
16759
|
// ../api-routes/src/schedules.ts
|
|
16576
|
-
import
|
|
16577
|
-
import { and as and12, eq as
|
|
16760
|
+
import crypto13 from "crypto";
|
|
16761
|
+
import { and as and12, eq as eq18 } from "drizzle-orm";
|
|
16578
16762
|
function parseKindParam(raw) {
|
|
16579
16763
|
if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
|
|
16580
16764
|
const parsed = schedulableRunKindSchema.safeParse(raw);
|
|
@@ -16601,7 +16785,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
16601
16785
|
if (!sourceId) {
|
|
16602
16786
|
throw validationError('"sourceId" is required when kind is "traffic-sync"');
|
|
16603
16787
|
}
|
|
16604
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
16788
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq18(trafficSources.id, sourceId)).get();
|
|
16605
16789
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
16606
16790
|
throw notFound("Traffic source", sourceId);
|
|
16607
16791
|
}
|
|
@@ -16643,7 +16827,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
16643
16827
|
}
|
|
16644
16828
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16645
16829
|
const enabledBool = enabled !== false;
|
|
16646
|
-
const existing = app.db.select().from(schedules).where(and12(
|
|
16830
|
+
const existing = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
|
|
16647
16831
|
if (existing) {
|
|
16648
16832
|
app.db.update(schedules).set({
|
|
16649
16833
|
cronExpr,
|
|
@@ -16653,10 +16837,10 @@ async function scheduleRoutes(app, opts) {
|
|
|
16653
16837
|
sourceId: sourceId ?? null,
|
|
16654
16838
|
enabled: enabledBool,
|
|
16655
16839
|
updatedAt: now
|
|
16656
|
-
}).where(
|
|
16840
|
+
}).where(eq18(schedules.id, existing.id)).run();
|
|
16657
16841
|
} else {
|
|
16658
16842
|
app.db.insert(schedules).values({
|
|
16659
|
-
id:
|
|
16843
|
+
id: crypto13.randomUUID(),
|
|
16660
16844
|
projectId: project.id,
|
|
16661
16845
|
kind,
|
|
16662
16846
|
cronExpr,
|
|
@@ -16677,13 +16861,13 @@ async function scheduleRoutes(app, opts) {
|
|
|
16677
16861
|
diff: { kind, cronExpr, preset, timezone, providers, sourceId }
|
|
16678
16862
|
});
|
|
16679
16863
|
opts.onScheduleUpdated?.("upsert", project.id, kind);
|
|
16680
|
-
const schedule = app.db.select().from(schedules).where(and12(
|
|
16864
|
+
const schedule = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
|
|
16681
16865
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
16682
16866
|
});
|
|
16683
16867
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
16684
16868
|
const project = resolveProject(app.db, request.params.name);
|
|
16685
16869
|
const kind = parseKindParam(request.query?.kind);
|
|
16686
|
-
const schedule = app.db.select().from(schedules).where(and12(
|
|
16870
|
+
const schedule = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
|
|
16687
16871
|
if (!schedule) {
|
|
16688
16872
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
16689
16873
|
}
|
|
@@ -16692,11 +16876,11 @@ async function scheduleRoutes(app, opts) {
|
|
|
16692
16876
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
16693
16877
|
const project = resolveProject(app.db, request.params.name);
|
|
16694
16878
|
const kind = parseKindParam(request.query?.kind);
|
|
16695
|
-
const schedule = app.db.select().from(schedules).where(and12(
|
|
16879
|
+
const schedule = app.db.select().from(schedules).where(and12(eq18(schedules.projectId, project.id), eq18(schedules.kind, kind))).get();
|
|
16696
16880
|
if (!schedule) {
|
|
16697
16881
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
16698
16882
|
}
|
|
16699
|
-
app.db.delete(schedules).where(
|
|
16883
|
+
app.db.delete(schedules).where(eq18(schedules.id, schedule.id)).run();
|
|
16700
16884
|
writeAuditLog(app.db, {
|
|
16701
16885
|
projectId: project.id,
|
|
16702
16886
|
actor: "api",
|
|
@@ -16728,8 +16912,8 @@ function formatSchedule(row) {
|
|
|
16728
16912
|
}
|
|
16729
16913
|
|
|
16730
16914
|
// ../api-routes/src/notifications.ts
|
|
16731
|
-
import
|
|
16732
|
-
import { eq as
|
|
16915
|
+
import crypto14 from "crypto";
|
|
16916
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
16733
16917
|
var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
|
|
16734
16918
|
async function notificationRoutes(app, opts = {}) {
|
|
16735
16919
|
const allowLoopback = opts.allowLoopbackWebhooks === true;
|
|
@@ -16748,8 +16932,8 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
16748
16932
|
throw validationError(`Invalid event(s): ${invalid.join(", ")}. Must be one of: ${VALID_EVENTS.join(", ")}`);
|
|
16749
16933
|
}
|
|
16750
16934
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16751
|
-
const id =
|
|
16752
|
-
const webhookSecret =
|
|
16935
|
+
const id = crypto14.randomUUID();
|
|
16936
|
+
const webhookSecret = crypto14.randomBytes(32).toString("hex");
|
|
16753
16937
|
app.db.insert(notifications).values({
|
|
16754
16938
|
id,
|
|
16755
16939
|
projectId: project.id,
|
|
@@ -16769,22 +16953,22 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
16769
16953
|
diff: { channel, ...redactNotificationUrl(url), events }
|
|
16770
16954
|
});
|
|
16771
16955
|
return reply.status(201).send({
|
|
16772
|
-
...formatNotification(app.db.select().from(notifications).where(
|
|
16956
|
+
...formatNotification(app.db.select().from(notifications).where(eq19(notifications.id, id)).get()),
|
|
16773
16957
|
webhookSecret
|
|
16774
16958
|
});
|
|
16775
16959
|
});
|
|
16776
16960
|
app.get("/projects/:name/notifications", async (request, reply) => {
|
|
16777
16961
|
const project = resolveProject(app.db, request.params.name);
|
|
16778
|
-
const rows = app.db.select().from(notifications).where(
|
|
16962
|
+
const rows = app.db.select().from(notifications).where(eq19(notifications.projectId, project.id)).all();
|
|
16779
16963
|
return reply.send(rows.map(formatNotification));
|
|
16780
16964
|
});
|
|
16781
16965
|
app.delete("/projects/:name/notifications/:id", async (request, reply) => {
|
|
16782
16966
|
const project = resolveProject(app.db, request.params.name);
|
|
16783
|
-
const notification = app.db.select().from(notifications).where(
|
|
16967
|
+
const notification = app.db.select().from(notifications).where(eq19(notifications.id, request.params.id)).get();
|
|
16784
16968
|
if (!notification || notification.projectId !== project.id) {
|
|
16785
16969
|
throw notFound("Notification", request.params.id);
|
|
16786
16970
|
}
|
|
16787
|
-
app.db.delete(notifications).where(
|
|
16971
|
+
app.db.delete(notifications).where(eq19(notifications.id, notification.id)).run();
|
|
16788
16972
|
writeAuditLog(app.db, {
|
|
16789
16973
|
projectId: project.id,
|
|
16790
16974
|
actor: "api",
|
|
@@ -16796,7 +16980,7 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
16796
16980
|
});
|
|
16797
16981
|
app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
|
|
16798
16982
|
const project = resolveProject(app.db, request.params.name);
|
|
16799
|
-
const notification = app.db.select().from(notifications).where(
|
|
16983
|
+
const notification = app.db.select().from(notifications).where(eq19(notifications.id, request.params.id)).get();
|
|
16800
16984
|
if (!notification || notification.projectId !== project.id) {
|
|
16801
16985
|
throw notFound("Notification", request.params.id);
|
|
16802
16986
|
}
|
|
@@ -16848,8 +17032,8 @@ function formatNotification(row) {
|
|
|
16848
17032
|
}
|
|
16849
17033
|
|
|
16850
17034
|
// ../api-routes/src/google.ts
|
|
16851
|
-
import
|
|
16852
|
-
import { eq as
|
|
17035
|
+
import crypto17 from "crypto";
|
|
17036
|
+
import { eq as eq20, and as and13, desc as desc10, sql as sql8, inArray as inArray9 } from "drizzle-orm";
|
|
16853
17037
|
|
|
16854
17038
|
// ../api-routes/src/gbp-summary.ts
|
|
16855
17039
|
function computeMetricTotals(rows) {
|
|
@@ -17351,7 +17535,7 @@ async function inspectUrl(accessToken, inspectionUrl, siteUrl) {
|
|
|
17351
17535
|
}
|
|
17352
17536
|
|
|
17353
17537
|
// ../integration-google-analytics/src/ga4-client.ts
|
|
17354
|
-
import
|
|
17538
|
+
import crypto15 from "crypto";
|
|
17355
17539
|
|
|
17356
17540
|
// ../integration-google-analytics/src/constants.ts
|
|
17357
17541
|
var GA4_DATA_API_BASE = "https://analyticsdata.googleapis.com/v1beta";
|
|
@@ -17458,7 +17642,7 @@ function createServiceAccountJwt(clientEmail, privateKey, scope) {
|
|
|
17458
17642
|
const headerB64 = encode(header);
|
|
17459
17643
|
const payloadB64 = encode(payload);
|
|
17460
17644
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
17461
|
-
const sign =
|
|
17645
|
+
const sign = crypto15.createSign("RSA-SHA256");
|
|
17462
17646
|
sign.update(signingInput);
|
|
17463
17647
|
const signature = sign.sign(privateKey, "base64url");
|
|
17464
17648
|
return `${signingInput}.${signature}`;
|
|
@@ -18298,7 +18482,7 @@ async function listPlaceActionLinks(accessToken, locationName, opts = {}) {
|
|
|
18298
18482
|
}
|
|
18299
18483
|
|
|
18300
18484
|
// ../integration-google-business-profile/src/lodging-client.ts
|
|
18301
|
-
import
|
|
18485
|
+
import crypto16 from "crypto";
|
|
18302
18486
|
async function getLodging(accessToken, locationName, opts = {}) {
|
|
18303
18487
|
const url = `${GBP_LODGING_BASE}/${locationName}/lodging?readMask=*`;
|
|
18304
18488
|
try {
|
|
@@ -18325,7 +18509,7 @@ function isPopulated(value) {
|
|
|
18325
18509
|
return true;
|
|
18326
18510
|
}
|
|
18327
18511
|
function hashLodging(lodging) {
|
|
18328
|
-
return
|
|
18512
|
+
return crypto16.createHash("sha256").update(stableStringify2(lodging)).digest("hex");
|
|
18329
18513
|
}
|
|
18330
18514
|
function stableStringify2(value) {
|
|
18331
18515
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
@@ -18347,7 +18531,7 @@ function scopesForConnectionType(type) {
|
|
|
18347
18531
|
}
|
|
18348
18532
|
}
|
|
18349
18533
|
function signState(payload, secret) {
|
|
18350
|
-
return
|
|
18534
|
+
return crypto17.createHmac("sha256", secret).update(payload).digest("hex");
|
|
18351
18535
|
}
|
|
18352
18536
|
function buildSignedState(data, secret) {
|
|
18353
18537
|
const payload = JSON.stringify(data);
|
|
@@ -18358,7 +18542,7 @@ function verifySignedState(encoded, secret) {
|
|
|
18358
18542
|
try {
|
|
18359
18543
|
const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
|
|
18360
18544
|
const expected = signState(payload, secret);
|
|
18361
|
-
if (!
|
|
18545
|
+
if (!crypto17.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
|
|
18362
18546
|
return JSON.parse(payload);
|
|
18363
18547
|
} catch {
|
|
18364
18548
|
return null;
|
|
@@ -18513,7 +18697,7 @@ async function googleRoutes(app, opts) {
|
|
|
18513
18697
|
if (!projectId) {
|
|
18514
18698
|
return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
|
|
18515
18699
|
}
|
|
18516
|
-
const project = app.db.select().from(projects).where(
|
|
18700
|
+
const project = app.db.select().from(projects).where(eq20(projects.id, projectId)).get();
|
|
18517
18701
|
if (!project) {
|
|
18518
18702
|
return reply.status(400).send("Project no longer exists. Restart the connect flow.");
|
|
18519
18703
|
}
|
|
@@ -18628,7 +18812,7 @@ async function googleRoutes(app, opts) {
|
|
|
18628
18812
|
throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
|
|
18629
18813
|
}
|
|
18630
18814
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18631
|
-
const runId =
|
|
18815
|
+
const runId = crypto17.randomUUID();
|
|
18632
18816
|
app.db.insert(runs).values({
|
|
18633
18817
|
id: runId,
|
|
18634
18818
|
projectId: project.id,
|
|
@@ -18641,14 +18825,14 @@ async function googleRoutes(app, opts) {
|
|
|
18641
18825
|
if (opts.onGscSyncRequested) {
|
|
18642
18826
|
opts.onGscSyncRequested(runId, project.id, { days, full });
|
|
18643
18827
|
}
|
|
18644
|
-
const run = app.db.select().from(runs).where(
|
|
18828
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
18645
18829
|
return run;
|
|
18646
18830
|
});
|
|
18647
18831
|
app.get("/projects/:name/google/gsc/performance", async (request) => {
|
|
18648
18832
|
const project = resolveProject(app.db, request.params.name);
|
|
18649
18833
|
const { startDate, endDate, query, page, limit, offset } = request.query;
|
|
18650
18834
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
18651
|
-
const conditions = [
|
|
18835
|
+
const conditions = [eq20(gscSearchData.projectId, project.id)];
|
|
18652
18836
|
if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
|
|
18653
18837
|
else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
|
|
18654
18838
|
if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -18656,7 +18840,7 @@ async function googleRoutes(app, opts) {
|
|
|
18656
18840
|
if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
|
|
18657
18841
|
const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
|
|
18658
18842
|
const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
|
|
18659
|
-
const rows = app.db.select().from(gscSearchData).where(and13(...conditions)).orderBy(
|
|
18843
|
+
const rows = app.db.select().from(gscSearchData).where(and13(...conditions)).orderBy(desc10(gscSearchData.date)).limit(limitVal).offset(offsetVal).all();
|
|
18660
18844
|
return rows.map((r) => ({
|
|
18661
18845
|
date: r.date,
|
|
18662
18846
|
query: r.query,
|
|
@@ -18673,7 +18857,7 @@ async function googleRoutes(app, opts) {
|
|
|
18673
18857
|
const project = resolveProject(app.db, request.params.name);
|
|
18674
18858
|
const { startDate, endDate } = request.query;
|
|
18675
18859
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
18676
|
-
const conditions = [
|
|
18860
|
+
const conditions = [eq20(gscSearchData.projectId, project.id)];
|
|
18677
18861
|
if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
|
|
18678
18862
|
else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
|
|
18679
18863
|
if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -18721,7 +18905,7 @@ async function googleRoutes(app, opts) {
|
|
|
18721
18905
|
const mob = ir.mobileUsabilityResult;
|
|
18722
18906
|
const rich = ir.richResultsResult;
|
|
18723
18907
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18724
|
-
const id =
|
|
18908
|
+
const id = crypto17.randomUUID();
|
|
18725
18909
|
app.db.insert(gscUrlInspections).values({
|
|
18726
18910
|
id,
|
|
18727
18911
|
projectId: project.id,
|
|
@@ -18759,9 +18943,9 @@ async function googleRoutes(app, opts) {
|
|
|
18759
18943
|
app.get("/projects/:name/google/gsc/inspections", async (request) => {
|
|
18760
18944
|
const project = resolveProject(app.db, request.params.name);
|
|
18761
18945
|
const { url, limit } = request.query;
|
|
18762
|
-
const conditions = [
|
|
18763
|
-
if (url) conditions.push(
|
|
18764
|
-
const rows = app.db.select().from(gscUrlInspections).where(and13(...conditions)).orderBy(
|
|
18946
|
+
const conditions = [eq20(gscUrlInspections.projectId, project.id)];
|
|
18947
|
+
if (url) conditions.push(eq20(gscUrlInspections.url, url));
|
|
18948
|
+
const rows = app.db.select().from(gscUrlInspections).where(and13(...conditions)).orderBy(desc10(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
|
|
18765
18949
|
return rows.map((r) => ({
|
|
18766
18950
|
id: r.id,
|
|
18767
18951
|
url: r.url,
|
|
@@ -18780,7 +18964,7 @@ async function googleRoutes(app, opts) {
|
|
|
18780
18964
|
});
|
|
18781
18965
|
app.get("/projects/:name/google/gsc/deindexed", async (request) => {
|
|
18782
18966
|
const project = resolveProject(app.db, request.params.name);
|
|
18783
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
18967
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, project.id)).orderBy(desc10(gscUrlInspections.inspectedAt)).all();
|
|
18784
18968
|
const byUrl = /* @__PURE__ */ new Map();
|
|
18785
18969
|
for (const row of allInspections) {
|
|
18786
18970
|
const existing = byUrl.get(row.url);
|
|
@@ -18808,7 +18992,7 @@ async function googleRoutes(app, opts) {
|
|
|
18808
18992
|
});
|
|
18809
18993
|
app.get("/projects/:name/google/gsc/coverage", async (request) => {
|
|
18810
18994
|
const project = resolveProject(app.db, request.params.name);
|
|
18811
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
18995
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, project.id)).orderBy(desc10(gscUrlInspections.inspectedAt)).all();
|
|
18812
18996
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
18813
18997
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
18814
18998
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -18857,7 +19041,7 @@ async function googleRoutes(app, opts) {
|
|
|
18857
19041
|
const total = latestByUrl.size;
|
|
18858
19042
|
const indexed = indexedUrls.length;
|
|
18859
19043
|
const notIndexed = notIndexedUrls.length;
|
|
18860
|
-
const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(
|
|
19044
|
+
const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq20(gscCoverageSnapshots.projectId, project.id)).orderBy(desc10(gscCoverageSnapshots.createdAt)).limit(1).get();
|
|
18861
19045
|
const lastSyncedAt = latestSnapshot?.createdAt ?? null;
|
|
18862
19046
|
const formatRow = (r) => ({
|
|
18863
19047
|
id: r.id,
|
|
@@ -18908,7 +19092,7 @@ async function googleRoutes(app, opts) {
|
|
|
18908
19092
|
const project = resolveProject(app.db, request.params.name);
|
|
18909
19093
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
18910
19094
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
18911
|
-
const rows = app.db.select().from(gscCoverageSnapshots).where(
|
|
19095
|
+
const rows = app.db.select().from(gscCoverageSnapshots).where(eq20(gscCoverageSnapshots.projectId, project.id)).orderBy(desc10(gscCoverageSnapshots.date)).limit(limit).all();
|
|
18912
19096
|
return rows.map((r) => ({
|
|
18913
19097
|
date: r.date,
|
|
18914
19098
|
indexed: r.indexed,
|
|
@@ -18956,7 +19140,7 @@ async function googleRoutes(app, opts) {
|
|
|
18956
19140
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18957
19141
|
});
|
|
18958
19142
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18959
|
-
const runId =
|
|
19143
|
+
const runId = crypto17.randomUUID();
|
|
18960
19144
|
app.db.insert(runs).values({
|
|
18961
19145
|
id: runId,
|
|
18962
19146
|
projectId: project.id,
|
|
@@ -18968,7 +19152,7 @@ async function googleRoutes(app, opts) {
|
|
|
18968
19152
|
if (opts.onInspectSitemapRequested) {
|
|
18969
19153
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
|
|
18970
19154
|
}
|
|
18971
|
-
const run = app.db.select().from(runs).where(
|
|
19155
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
18972
19156
|
return { sitemaps, primarySitemapUrl: sitemapUrl, run };
|
|
18973
19157
|
});
|
|
18974
19158
|
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
|
|
@@ -18982,7 +19166,7 @@ async function googleRoutes(app, opts) {
|
|
|
18982
19166
|
throw validationError("No GSC property configured for this connection");
|
|
18983
19167
|
}
|
|
18984
19168
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18985
|
-
const runId =
|
|
19169
|
+
const runId = crypto17.randomUUID();
|
|
18986
19170
|
app.db.insert(runs).values({
|
|
18987
19171
|
id: runId,
|
|
18988
19172
|
projectId: project.id,
|
|
@@ -18995,7 +19179,7 @@ async function googleRoutes(app, opts) {
|
|
|
18995
19179
|
if (opts.onInspectSitemapRequested) {
|
|
18996
19180
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
|
|
18997
19181
|
}
|
|
18998
|
-
const run = app.db.select().from(runs).where(
|
|
19182
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
18999
19183
|
return run;
|
|
19000
19184
|
});
|
|
19001
19185
|
app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
|
|
@@ -19042,7 +19226,7 @@ async function googleRoutes(app, opts) {
|
|
|
19042
19226
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
19043
19227
|
let urlsToNotify = request.body?.urls ?? [];
|
|
19044
19228
|
if (request.body?.allUnindexed) {
|
|
19045
|
-
const allInspections = app.db.select().from(gscUrlInspections).where(
|
|
19229
|
+
const allInspections = app.db.select().from(gscUrlInspections).where(eq20(gscUrlInspections.projectId, project.id)).orderBy(desc10(gscUrlInspections.inspectedAt)).all();
|
|
19046
19230
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
19047
19231
|
for (const row of allInspections) {
|
|
19048
19232
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -19153,7 +19337,7 @@ async function googleRoutes(app, opts) {
|
|
|
19153
19337
|
};
|
|
19154
19338
|
}
|
|
19155
19339
|
function listSelectionResponse(projectId) {
|
|
19156
|
-
const rows = app.db.select().from(gbpLocations).where(
|
|
19340
|
+
const rows = app.db.select().from(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).all();
|
|
19157
19341
|
const dtos = rows.map(rowToDto2);
|
|
19158
19342
|
return {
|
|
19159
19343
|
locations: dtos,
|
|
@@ -19162,15 +19346,15 @@ async function googleRoutes(app, opts) {
|
|
|
19162
19346
|
};
|
|
19163
19347
|
}
|
|
19164
19348
|
function clearGbpProjectData(tx, projectId) {
|
|
19165
|
-
tx.delete(gbpDailyMetrics).where(
|
|
19166
|
-
tx.delete(gbpKeywordImpressions).where(
|
|
19167
|
-
tx.delete(gbpKeywordMonthly).where(
|
|
19168
|
-
tx.delete(gbpPlaceActions).where(
|
|
19169
|
-
tx.delete(gbpLodgingSnapshots).where(
|
|
19170
|
-
tx.delete(gbpLocations).where(
|
|
19349
|
+
tx.delete(gbpDailyMetrics).where(eq20(gbpDailyMetrics.projectId, projectId)).run();
|
|
19350
|
+
tx.delete(gbpKeywordImpressions).where(eq20(gbpKeywordImpressions.projectId, projectId)).run();
|
|
19351
|
+
tx.delete(gbpKeywordMonthly).where(eq20(gbpKeywordMonthly.projectId, projectId)).run();
|
|
19352
|
+
tx.delete(gbpPlaceActions).where(eq20(gbpPlaceActions.projectId, projectId)).run();
|
|
19353
|
+
tx.delete(gbpLodgingSnapshots).where(eq20(gbpLodgingSnapshots.projectId, projectId)).run();
|
|
19354
|
+
tx.delete(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).run();
|
|
19171
19355
|
}
|
|
19172
19356
|
function currentProjectAccount(projectId) {
|
|
19173
|
-
const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(
|
|
19357
|
+
const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).limit(1).get();
|
|
19174
19358
|
return row?.accountName ?? null;
|
|
19175
19359
|
}
|
|
19176
19360
|
app.post("/projects/:name/gbp/locations/discover", async (request) => {
|
|
@@ -19240,7 +19424,7 @@ async function googleRoutes(app, opts) {
|
|
|
19240
19424
|
app.db.transaction((tx) => {
|
|
19241
19425
|
if (switching) clearGbpProjectData(tx, project.id);
|
|
19242
19426
|
for (const remote of remoteLocations) {
|
|
19243
|
-
const existing = tx.select().from(gbpLocations).where(and13(
|
|
19427
|
+
const existing = tx.select().from(gbpLocations).where(and13(eq20(gbpLocations.projectId, project.id), eq20(gbpLocations.locationName, remote.name))).get();
|
|
19244
19428
|
if (existing) {
|
|
19245
19429
|
tx.update(gbpLocations).set({
|
|
19246
19430
|
accountName,
|
|
@@ -19251,10 +19435,10 @@ async function googleRoutes(app, opts) {
|
|
|
19251
19435
|
placeId: remote.metadata?.placeId ?? null,
|
|
19252
19436
|
mapsUri: remote.metadata?.mapsUri ?? null,
|
|
19253
19437
|
updatedAt: now
|
|
19254
|
-
}).where(
|
|
19438
|
+
}).where(eq20(gbpLocations.id, existing.id)).run();
|
|
19255
19439
|
} else {
|
|
19256
19440
|
tx.insert(gbpLocations).values({
|
|
19257
|
-
id:
|
|
19441
|
+
id: crypto17.randomUUID(),
|
|
19258
19442
|
projectId: project.id,
|
|
19259
19443
|
accountName,
|
|
19260
19444
|
locationName: remote.name,
|
|
@@ -19334,11 +19518,11 @@ async function googleRoutes(app, opts) {
|
|
|
19334
19518
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
|
|
19335
19519
|
}
|
|
19336
19520
|
const { selected } = parsed.data;
|
|
19337
|
-
const existing = app.db.select().from(gbpLocations).where(and13(
|
|
19521
|
+
const existing = app.db.select().from(gbpLocations).where(and13(eq20(gbpLocations.projectId, project.id), eq20(gbpLocations.locationName, locationName))).get();
|
|
19338
19522
|
if (!existing) throw notFound("GBP location", locationName);
|
|
19339
19523
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19340
19524
|
app.db.transaction((tx) => {
|
|
19341
|
-
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(
|
|
19525
|
+
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq20(gbpLocations.id, existing.id)).run();
|
|
19342
19526
|
writeAuditLog(tx, {
|
|
19343
19527
|
projectId: project.id,
|
|
19344
19528
|
actor: "api",
|
|
@@ -19347,7 +19531,7 @@ async function googleRoutes(app, opts) {
|
|
|
19347
19531
|
entityId: locationName
|
|
19348
19532
|
});
|
|
19349
19533
|
});
|
|
19350
|
-
const refreshed = app.db.select().from(gbpLocations).where(
|
|
19534
|
+
const refreshed = app.db.select().from(gbpLocations).where(eq20(gbpLocations.id, existing.id)).get();
|
|
19351
19535
|
return rowToDto2(refreshed);
|
|
19352
19536
|
});
|
|
19353
19537
|
app.delete("/projects/:name/gbp/connection", async (request, reply) => {
|
|
@@ -19377,7 +19561,7 @@ async function googleRoutes(app, opts) {
|
|
|
19377
19561
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid sync request");
|
|
19378
19562
|
}
|
|
19379
19563
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19380
|
-
const runId =
|
|
19564
|
+
const runId = crypto17.randomUUID();
|
|
19381
19565
|
app.db.insert(runs).values({
|
|
19382
19566
|
id: runId,
|
|
19383
19567
|
projectId: project.id,
|
|
@@ -19391,10 +19575,10 @@ async function googleRoutes(app, opts) {
|
|
|
19391
19575
|
});
|
|
19392
19576
|
app.get("/projects/:name/gbp/metrics", async (request) => {
|
|
19393
19577
|
const project = resolveProject(app.db, request.params.name);
|
|
19394
|
-
const conditions = [
|
|
19395
|
-
if (request.query.locationName) conditions.push(
|
|
19396
|
-
if (request.query.metric) conditions.push(
|
|
19397
|
-
const rows = app.db.select().from(gbpDailyMetrics).where(and13(...conditions)).orderBy(
|
|
19578
|
+
const conditions = [eq20(gbpDailyMetrics.projectId, project.id)];
|
|
19579
|
+
if (request.query.locationName) conditions.push(eq20(gbpDailyMetrics.locationName, request.query.locationName));
|
|
19580
|
+
if (request.query.metric) conditions.push(eq20(gbpDailyMetrics.metric, request.query.metric));
|
|
19581
|
+
const rows = app.db.select().from(gbpDailyMetrics).where(and13(...conditions)).orderBy(desc10(gbpDailyMetrics.date)).all();
|
|
19398
19582
|
return {
|
|
19399
19583
|
metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
|
|
19400
19584
|
total: rows.length
|
|
@@ -19402,8 +19586,8 @@ async function googleRoutes(app, opts) {
|
|
|
19402
19586
|
});
|
|
19403
19587
|
app.get("/projects/:name/gbp/keywords", async (request) => {
|
|
19404
19588
|
const project = resolveProject(app.db, request.params.name);
|
|
19405
|
-
const conditions = [
|
|
19406
|
-
if (request.query.locationName) conditions.push(
|
|
19589
|
+
const conditions = [eq20(gbpKeywordImpressions.projectId, project.id)];
|
|
19590
|
+
if (request.query.locationName) conditions.push(eq20(gbpKeywordImpressions.locationName, request.query.locationName));
|
|
19407
19591
|
const rows = app.db.select().from(gbpKeywordImpressions).where(and13(...conditions)).all();
|
|
19408
19592
|
rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
|
|
19409
19593
|
const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
|
|
@@ -19422,8 +19606,8 @@ async function googleRoutes(app, opts) {
|
|
|
19422
19606
|
});
|
|
19423
19607
|
app.get("/projects/:name/gbp/place-actions", async (request) => {
|
|
19424
19608
|
const project = resolveProject(app.db, request.params.name);
|
|
19425
|
-
const conditions = [
|
|
19426
|
-
if (request.query.locationName) conditions.push(
|
|
19609
|
+
const conditions = [eq20(gbpPlaceActions.projectId, project.id)];
|
|
19610
|
+
if (request.query.locationName) conditions.push(eq20(gbpPlaceActions.locationName, request.query.locationName));
|
|
19427
19611
|
const rows = app.db.select().from(gbpPlaceActions).where(and13(...conditions)).all();
|
|
19428
19612
|
return {
|
|
19429
19613
|
placeActions: rows.map((r) => ({
|
|
@@ -19439,9 +19623,9 @@ async function googleRoutes(app, opts) {
|
|
|
19439
19623
|
});
|
|
19440
19624
|
app.get("/projects/:name/gbp/lodging", async (request) => {
|
|
19441
19625
|
const project = resolveProject(app.db, request.params.name);
|
|
19442
|
-
const conditions = [
|
|
19443
|
-
if (request.query.locationName) conditions.push(
|
|
19444
|
-
const rows = app.db.select().from(gbpLodgingSnapshots).where(and13(...conditions)).orderBy(
|
|
19626
|
+
const conditions = [eq20(gbpLodgingSnapshots.projectId, project.id)];
|
|
19627
|
+
if (request.query.locationName) conditions.push(eq20(gbpLodgingSnapshots.locationName, request.query.locationName));
|
|
19628
|
+
const rows = app.db.select().from(gbpLodgingSnapshots).where(and13(...conditions)).orderBy(desc10(gbpLodgingSnapshots.syncedAt)).all();
|
|
19445
19629
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
19446
19630
|
for (const row of rows) {
|
|
19447
19631
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -19456,9 +19640,9 @@ async function googleRoutes(app, opts) {
|
|
|
19456
19640
|
});
|
|
19457
19641
|
app.get("/projects/:name/gbp/places", async (request) => {
|
|
19458
19642
|
const project = resolveProject(app.db, request.params.name);
|
|
19459
|
-
const conditions = [
|
|
19460
|
-
if (request.query.locationName) conditions.push(
|
|
19461
|
-
const rows = app.db.select().from(gbpPlaceDetails).where(and13(...conditions)).orderBy(
|
|
19643
|
+
const conditions = [eq20(gbpPlaceDetails.projectId, project.id)];
|
|
19644
|
+
if (request.query.locationName) conditions.push(eq20(gbpPlaceDetails.locationName, request.query.locationName));
|
|
19645
|
+
const rows = app.db.select().from(gbpPlaceDetails).where(and13(...conditions)).orderBy(desc10(gbpPlaceDetails.syncedAt)).all();
|
|
19462
19646
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
19463
19647
|
for (const row of rows) {
|
|
19464
19648
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -19477,7 +19661,7 @@ async function googleRoutes(app, opts) {
|
|
|
19477
19661
|
app.get("/projects/:name/gbp/summary", async (request) => {
|
|
19478
19662
|
const project = resolveProject(app.db, request.params.name);
|
|
19479
19663
|
const locationName = request.query.locationName ?? null;
|
|
19480
|
-
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and13(
|
|
19664
|
+
const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and13(eq20(gbpLocations.projectId, project.id), eq20(gbpLocations.selected, true))).all().map((r) => r.n);
|
|
19481
19665
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
19482
19666
|
if (locationNames.length === 0) {
|
|
19483
19667
|
return buildGbpSummary({
|
|
@@ -19490,10 +19674,10 @@ async function googleRoutes(app, opts) {
|
|
|
19490
19674
|
lodging: []
|
|
19491
19675
|
});
|
|
19492
19676
|
}
|
|
19493
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(and13(
|
|
19494
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and13(
|
|
19495
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and13(
|
|
19496
|
-
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and13(
|
|
19677
|
+
const metricRows = app.db.select().from(gbpDailyMetrics).where(and13(eq20(gbpDailyMetrics.projectId, project.id), inArray9(gbpDailyMetrics.locationName, locationNames))).all();
|
|
19678
|
+
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and13(eq20(gbpKeywordImpressions.projectId, project.id), inArray9(gbpKeywordImpressions.locationName, locationNames))).all();
|
|
19679
|
+
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and13(eq20(gbpPlaceActions.projectId, project.id), inArray9(gbpPlaceActions.locationName, locationNames))).all();
|
|
19680
|
+
const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and13(eq20(gbpLodgingSnapshots.projectId, project.id), inArray9(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc10(gbpLodgingSnapshots.syncedAt)).all();
|
|
19497
19681
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
19498
19682
|
for (const row of lodgingRows) {
|
|
19499
19683
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -19513,8 +19697,8 @@ async function googleRoutes(app, opts) {
|
|
|
19513
19697
|
}
|
|
19514
19698
|
|
|
19515
19699
|
// ../api-routes/src/bing.ts
|
|
19516
|
-
import
|
|
19517
|
-
import { eq as
|
|
19700
|
+
import crypto18 from "crypto";
|
|
19701
|
+
import { eq as eq21, and as and14, desc as desc11 } from "drizzle-orm";
|
|
19518
19702
|
|
|
19519
19703
|
// ../integration-bing/src/constants.ts
|
|
19520
19704
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -19842,7 +20026,7 @@ async function bingRoutes(app, opts) {
|
|
|
19842
20026
|
const store = requireConnectionStore();
|
|
19843
20027
|
const project = resolveProject(app.db, request.params.name);
|
|
19844
20028
|
requireConnection(store, project.canonicalDomain);
|
|
19845
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
20029
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq21(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
|
|
19846
20030
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
19847
20031
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
19848
20032
|
for (const row of allInspections) {
|
|
@@ -19899,7 +20083,7 @@ async function bingRoutes(app, opts) {
|
|
|
19899
20083
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
19900
20084
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19901
20085
|
app.db.insert(bingCoverageSnapshots).values({
|
|
19902
|
-
id:
|
|
20086
|
+
id: crypto18.randomUUID(),
|
|
19903
20087
|
projectId: project.id,
|
|
19904
20088
|
syncRunId: snapshotRunId,
|
|
19905
20089
|
date: snapshotDate,
|
|
@@ -19931,7 +20115,7 @@ async function bingRoutes(app, opts) {
|
|
|
19931
20115
|
const project = resolveProject(app.db, request.params.name);
|
|
19932
20116
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
19933
20117
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
19934
|
-
const rows = app.db.select().from(bingCoverageSnapshots).where(
|
|
20118
|
+
const rows = app.db.select().from(bingCoverageSnapshots).where(eq21(bingCoverageSnapshots.projectId, project.id)).orderBy(desc11(bingCoverageSnapshots.date)).limit(limit).all();
|
|
19935
20119
|
return rows.map((r) => ({
|
|
19936
20120
|
date: r.date,
|
|
19937
20121
|
indexed: r.indexed,
|
|
@@ -19943,8 +20127,8 @@ async function bingRoutes(app, opts) {
|
|
|
19943
20127
|
requireConnectionStore();
|
|
19944
20128
|
const project = resolveProject(app.db, request.params.name);
|
|
19945
20129
|
const { url, limit } = request.query;
|
|
19946
|
-
const whereClause = url ? and14(
|
|
19947
|
-
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(
|
|
20130
|
+
const whereClause = url ? and14(eq21(bingUrlInspections.projectId, project.id), eq21(bingUrlInspections.url, url)) : eq21(bingUrlInspections.projectId, project.id);
|
|
20131
|
+
const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc11(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
|
|
19948
20132
|
return filtered.map((r) => ({
|
|
19949
20133
|
id: r.id,
|
|
19950
20134
|
url: r.url,
|
|
@@ -19970,7 +20154,7 @@ async function bingRoutes(app, opts) {
|
|
|
19970
20154
|
throw validationError("url is required");
|
|
19971
20155
|
}
|
|
19972
20156
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19973
|
-
const runId =
|
|
20157
|
+
const runId = crypto18.randomUUID();
|
|
19974
20158
|
app.db.insert(runs).values({
|
|
19975
20159
|
id: runId,
|
|
19976
20160
|
projectId: project.id,
|
|
@@ -19991,7 +20175,7 @@ async function bingRoutes(app, opts) {
|
|
|
19991
20175
|
discoveryDate: result.DiscoveryDate ?? null
|
|
19992
20176
|
});
|
|
19993
20177
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19994
|
-
const id =
|
|
20178
|
+
const id = crypto18.randomUUID();
|
|
19995
20179
|
const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
|
|
19996
20180
|
const lastCrawledDate = parseBingDate(result.LastCrawledDate);
|
|
19997
20181
|
const inIndexDate = parseBingDate(result.InIndexDate);
|
|
@@ -20033,7 +20217,7 @@ async function bingRoutes(app, opts) {
|
|
|
20033
20217
|
anchorCount: result.AnchorCount ?? null,
|
|
20034
20218
|
discoveryDate
|
|
20035
20219
|
}).run();
|
|
20036
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
20220
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq21(runs.id, runId)).run();
|
|
20037
20221
|
return {
|
|
20038
20222
|
id,
|
|
20039
20223
|
url,
|
|
@@ -20049,7 +20233,7 @@ async function bingRoutes(app, opts) {
|
|
|
20049
20233
|
} catch (e) {
|
|
20050
20234
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20051
20235
|
bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
|
|
20052
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
20236
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
|
|
20053
20237
|
throw e;
|
|
20054
20238
|
}
|
|
20055
20239
|
});
|
|
@@ -20061,7 +20245,7 @@ async function bingRoutes(app, opts) {
|
|
|
20061
20245
|
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
20062
20246
|
}
|
|
20063
20247
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20064
|
-
const runId =
|
|
20248
|
+
const runId = crypto18.randomUUID();
|
|
20065
20249
|
app.db.insert(runs).values({
|
|
20066
20250
|
id: runId,
|
|
20067
20251
|
projectId: project.id,
|
|
@@ -20076,7 +20260,7 @@ async function bingRoutes(app, opts) {
|
|
|
20076
20260
|
} else {
|
|
20077
20261
|
bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
|
|
20078
20262
|
}
|
|
20079
|
-
const run = app.db.select().from(runs).where(
|
|
20263
|
+
const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
|
|
20080
20264
|
return run;
|
|
20081
20265
|
});
|
|
20082
20266
|
app.post("/projects/:name/bing/request-indexing", async (request) => {
|
|
@@ -20088,7 +20272,7 @@ async function bingRoutes(app, opts) {
|
|
|
20088
20272
|
}
|
|
20089
20273
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
20090
20274
|
if (request.body?.allUnindexed) {
|
|
20091
|
-
const allInspections = app.db.select().from(bingUrlInspections).where(
|
|
20275
|
+
const allInspections = app.db.select().from(bingUrlInspections).where(eq21(bingUrlInspections.projectId, project.id)).orderBy(desc11(bingUrlInspections.inspectedAt)).all();
|
|
20092
20276
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
20093
20277
|
for (const row of allInspections) {
|
|
20094
20278
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -20175,14 +20359,14 @@ async function bingRoutes(app, opts) {
|
|
|
20175
20359
|
import fs from "fs";
|
|
20176
20360
|
import path from "path";
|
|
20177
20361
|
import os from "os";
|
|
20178
|
-
import { eq as
|
|
20362
|
+
import { eq as eq22, and as and15 } from "drizzle-orm";
|
|
20179
20363
|
function getScreenshotDir() {
|
|
20180
20364
|
return path.join(os.homedir(), ".canonry", "screenshots");
|
|
20181
20365
|
}
|
|
20182
20366
|
async function cdpRoutes(app, opts) {
|
|
20183
20367
|
app.get("/screenshots/:snapshotId", async (request, reply) => {
|
|
20184
20368
|
const { snapshotId } = request.params;
|
|
20185
|
-
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(
|
|
20369
|
+
const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath }).from(querySnapshots).where(eq22(querySnapshots.id, snapshotId)).get();
|
|
20186
20370
|
if (!snapshot?.screenshotPath) {
|
|
20187
20371
|
const err = notFound("Screenshot", snapshotId);
|
|
20188
20372
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -20249,7 +20433,7 @@ async function cdpRoutes(app, opts) {
|
|
|
20249
20433
|
async (request, reply) => {
|
|
20250
20434
|
const project = resolveProject(app.db, request.params.name);
|
|
20251
20435
|
const { runId } = request.params;
|
|
20252
|
-
const run = app.db.select().from(runs).where(and15(
|
|
20436
|
+
const run = app.db.select().from(runs).where(and15(eq22(runs.id, runId), eq22(runs.projectId, project.id))).get();
|
|
20253
20437
|
if (!run) {
|
|
20254
20438
|
const err = notFound("Run", runId);
|
|
20255
20439
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -20262,8 +20446,8 @@ async function cdpRoutes(app, opts) {
|
|
|
20262
20446
|
citedDomains: querySnapshots.citedDomains,
|
|
20263
20447
|
screenshotPath: querySnapshots.screenshotPath,
|
|
20264
20448
|
rawResponse: querySnapshots.rawResponse
|
|
20265
|
-
}).from(querySnapshots).where(
|
|
20266
|
-
const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(
|
|
20449
|
+
}).from(querySnapshots).where(eq22(querySnapshots.runId, runId)).all());
|
|
20450
|
+
const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq22(queries.projectId, project.id)).all();
|
|
20267
20451
|
const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
|
|
20268
20452
|
const byQuery = /* @__PURE__ */ new Map();
|
|
20269
20453
|
for (const snap of snapshots) {
|
|
@@ -20345,8 +20529,8 @@ async function cdpRoutes(app, opts) {
|
|
|
20345
20529
|
}
|
|
20346
20530
|
|
|
20347
20531
|
// ../api-routes/src/ga.ts
|
|
20348
|
-
import
|
|
20349
|
-
import { eq as
|
|
20532
|
+
import crypto19 from "crypto";
|
|
20533
|
+
import { eq as eq23, desc as desc12, and as and16, sql as sql9 } from "drizzle-orm";
|
|
20350
20534
|
function gaLog(level, action, ctx) {
|
|
20351
20535
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
20352
20536
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -20553,10 +20737,10 @@ async function ga4Routes(app, opts) {
|
|
|
20553
20737
|
if (!saConn && !oauthConn) {
|
|
20554
20738
|
throw notFound("GA4 connection", project.name);
|
|
20555
20739
|
}
|
|
20556
|
-
app.db.delete(gaTrafficSnapshots).where(
|
|
20557
|
-
app.db.delete(gaTrafficSummaries).where(
|
|
20558
|
-
app.db.delete(gaAiReferrals).where(
|
|
20559
|
-
app.db.delete(gaSocialReferrals).where(
|
|
20740
|
+
app.db.delete(gaTrafficSnapshots).where(eq23(gaTrafficSnapshots.projectId, project.id)).run();
|
|
20741
|
+
app.db.delete(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).run();
|
|
20742
|
+
app.db.delete(gaAiReferrals).where(eq23(gaAiReferrals.projectId, project.id)).run();
|
|
20743
|
+
app.db.delete(gaSocialReferrals).where(eq23(gaSocialReferrals.projectId, project.id)).run();
|
|
20560
20744
|
const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
|
|
20561
20745
|
opts.ga4CredentialStore?.deleteConnection(project.name);
|
|
20562
20746
|
opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
|
|
@@ -20577,7 +20761,7 @@ async function ga4Routes(app, opts) {
|
|
|
20577
20761
|
if (!connected) {
|
|
20578
20762
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
20579
20763
|
}
|
|
20580
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
20764
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
20581
20765
|
return {
|
|
20582
20766
|
connected: true,
|
|
20583
20767
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -20601,7 +20785,7 @@ async function ga4Routes(app, opts) {
|
|
|
20601
20785
|
const syncAi = !only || only === "ai";
|
|
20602
20786
|
const syncSocial = !only || only === "social";
|
|
20603
20787
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20604
|
-
const runId =
|
|
20788
|
+
const runId = crypto19.randomUUID();
|
|
20605
20789
|
app.db.insert(runs).values({
|
|
20606
20790
|
id: runId,
|
|
20607
20791
|
projectId: project.id,
|
|
@@ -20642,14 +20826,14 @@ async function ga4Routes(app, opts) {
|
|
|
20642
20826
|
if (syncTraffic) {
|
|
20643
20827
|
tx.delete(gaTrafficSnapshots).where(
|
|
20644
20828
|
and16(
|
|
20645
|
-
|
|
20829
|
+
eq23(gaTrafficSnapshots.projectId, project.id),
|
|
20646
20830
|
sql9`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
20647
20831
|
sql9`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
20648
20832
|
)
|
|
20649
20833
|
).run();
|
|
20650
20834
|
for (const row of rows) {
|
|
20651
20835
|
tx.insert(gaTrafficSnapshots).values({
|
|
20652
|
-
id:
|
|
20836
|
+
id: crypto19.randomUUID(),
|
|
20653
20837
|
projectId: project.id,
|
|
20654
20838
|
date: row.date,
|
|
20655
20839
|
landingPage: row.landingPage,
|
|
@@ -20666,14 +20850,14 @@ async function ga4Routes(app, opts) {
|
|
|
20666
20850
|
if (syncAi) {
|
|
20667
20851
|
tx.delete(gaAiReferrals).where(
|
|
20668
20852
|
and16(
|
|
20669
|
-
|
|
20853
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
20670
20854
|
sql9`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
20671
20855
|
sql9`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
20672
20856
|
)
|
|
20673
20857
|
).run();
|
|
20674
20858
|
for (const row of aiReferrals) {
|
|
20675
20859
|
tx.insert(gaAiReferrals).values({
|
|
20676
|
-
id:
|
|
20860
|
+
id: crypto19.randomUUID(),
|
|
20677
20861
|
projectId: project.id,
|
|
20678
20862
|
date: row.date,
|
|
20679
20863
|
source: row.source,
|
|
@@ -20692,14 +20876,14 @@ async function ga4Routes(app, opts) {
|
|
|
20692
20876
|
if (syncSocial) {
|
|
20693
20877
|
tx.delete(gaSocialReferrals).where(
|
|
20694
20878
|
and16(
|
|
20695
|
-
|
|
20879
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
20696
20880
|
sql9`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
20697
20881
|
sql9`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
20698
20882
|
)
|
|
20699
20883
|
).run();
|
|
20700
20884
|
for (const row of socialReferrals) {
|
|
20701
20885
|
tx.insert(gaSocialReferrals).values({
|
|
20702
|
-
id:
|
|
20886
|
+
id: crypto19.randomUUID(),
|
|
20703
20887
|
projectId: project.id,
|
|
20704
20888
|
date: row.date,
|
|
20705
20889
|
source: row.source,
|
|
@@ -20713,9 +20897,9 @@ async function ga4Routes(app, opts) {
|
|
|
20713
20897
|
}
|
|
20714
20898
|
}
|
|
20715
20899
|
if (syncSummary) {
|
|
20716
|
-
tx.delete(gaTrafficSummaries).where(
|
|
20900
|
+
tx.delete(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).run();
|
|
20717
20901
|
tx.insert(gaTrafficSummaries).values({
|
|
20718
|
-
id:
|
|
20902
|
+
id: crypto19.randomUUID(),
|
|
20719
20903
|
projectId: project.id,
|
|
20720
20904
|
periodStart: summary.periodStart,
|
|
20721
20905
|
periodEnd: summary.periodEnd,
|
|
@@ -20725,10 +20909,10 @@ async function ga4Routes(app, opts) {
|
|
|
20725
20909
|
syncedAt: now,
|
|
20726
20910
|
syncRunId: runId
|
|
20727
20911
|
}).run();
|
|
20728
|
-
tx.delete(gaTrafficWindowSummaries).where(
|
|
20912
|
+
tx.delete(gaTrafficWindowSummaries).where(eq23(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
20729
20913
|
for (const ws of windowSummaries) {
|
|
20730
20914
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
20731
|
-
id:
|
|
20915
|
+
id: crypto19.randomUUID(),
|
|
20732
20916
|
projectId: project.id,
|
|
20733
20917
|
windowKey: ws.windowKey,
|
|
20734
20918
|
periodStart: ws.periodStart,
|
|
@@ -20743,7 +20927,7 @@ async function ga4Routes(app, opts) {
|
|
|
20743
20927
|
}
|
|
20744
20928
|
}
|
|
20745
20929
|
});
|
|
20746
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(
|
|
20930
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq23(runs.id, runId)).run();
|
|
20747
20931
|
const syncedComponents = only ? [
|
|
20748
20932
|
...syncTraffic ? ["traffic"] : [],
|
|
20749
20933
|
...syncSummary ? ["summary"] : [],
|
|
@@ -20772,7 +20956,7 @@ async function ga4Routes(app, opts) {
|
|
|
20772
20956
|
} catch (e) {
|
|
20773
20957
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20774
20958
|
gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
|
|
20775
|
-
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
20959
|
+
app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(runs.id, runId)).run();
|
|
20776
20960
|
throw e;
|
|
20777
20961
|
}
|
|
20778
20962
|
});
|
|
@@ -20783,11 +20967,11 @@ async function ga4Routes(app, opts) {
|
|
|
20783
20967
|
const window = parseWindow(request.query.window);
|
|
20784
20968
|
const cutoff = windowCutoff(window);
|
|
20785
20969
|
const cutoffDate = cutoff?.slice(0, 10) ?? null;
|
|
20786
|
-
const snapshotConditions = [
|
|
20970
|
+
const snapshotConditions = [eq23(gaTrafficSnapshots.projectId, project.id)];
|
|
20787
20971
|
if (cutoffDate) snapshotConditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
20788
|
-
const aiConditions = [
|
|
20972
|
+
const aiConditions = [eq23(gaAiReferrals.projectId, project.id)];
|
|
20789
20973
|
if (cutoffDate) aiConditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
20790
|
-
const socialConditions = [
|
|
20974
|
+
const socialConditions = [eq23(gaSocialReferrals.projectId, project.id)];
|
|
20791
20975
|
if (cutoffDate) socialConditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
20792
20976
|
const windowSummaryRow = cutoffDate ? app.db.select({
|
|
20793
20977
|
totalSessions: gaTrafficWindowSummaries.totalSessions,
|
|
@@ -20796,8 +20980,8 @@ async function ga4Routes(app, opts) {
|
|
|
20796
20980
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
20797
20981
|
}).from(gaTrafficWindowSummaries).where(
|
|
20798
20982
|
and16(
|
|
20799
|
-
|
|
20800
|
-
|
|
20983
|
+
eq23(gaTrafficWindowSummaries.projectId, project.id),
|
|
20984
|
+
eq23(gaTrafficWindowSummaries.windowKey, window)
|
|
20801
20985
|
)
|
|
20802
20986
|
).get() : null;
|
|
20803
20987
|
const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
|
|
@@ -20809,14 +20993,14 @@ async function ga4Routes(app, opts) {
|
|
|
20809
20993
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
20810
20994
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
20811
20995
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
20812
|
-
}).from(gaTrafficSummaries).where(
|
|
20996
|
+
}).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).get();
|
|
20813
20997
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
20814
20998
|
totalDirectSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
20815
20999
|
}).from(gaTrafficSnapshots).where(and16(...snapshotConditions)).get();
|
|
20816
21000
|
const summaryMeta = app.db.select({
|
|
20817
21001
|
periodStart: gaTrafficSummaries.periodStart,
|
|
20818
21002
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
20819
|
-
}).from(gaTrafficSummaries).where(
|
|
21003
|
+
}).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).get();
|
|
20820
21004
|
const rows = app.db.select({
|
|
20821
21005
|
landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
|
|
20822
21006
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
@@ -20875,7 +21059,7 @@ async function ga4Routes(app, opts) {
|
|
|
20875
21059
|
channelGroup: gaAiReferrals.channelGroup,
|
|
20876
21060
|
sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
20877
21061
|
users: sql9`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
20878
|
-
}).from(gaAiReferrals).where(and16(...aiConditions,
|
|
21062
|
+
}).from(gaAiReferrals).where(and16(...aiConditions, eq23(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
|
|
20879
21063
|
const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
|
|
20880
21064
|
let aiBySessionUsers = 0;
|
|
20881
21065
|
for (const row of aiBySessionRows) {
|
|
@@ -20894,7 +21078,7 @@ async function ga4Routes(app, opts) {
|
|
|
20894
21078
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
|
|
20895
21079
|
users: sql9`SUM(${gaSocialReferrals.users})`
|
|
20896
21080
|
}).from(gaSocialReferrals).where(and16(...socialConditions)).get();
|
|
20897
|
-
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(
|
|
21081
|
+
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).orderBy(desc12(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
20898
21082
|
const total = summaryRow?.totalSessions ?? 0;
|
|
20899
21083
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
20900
21084
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -20974,7 +21158,7 @@ async function ga4Routes(app, opts) {
|
|
|
20974
21158
|
const project = resolveProject(app.db, request.params.name);
|
|
20975
21159
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
20976
21160
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
20977
|
-
const conditions = [
|
|
21161
|
+
const conditions = [eq23(gaAiReferrals.projectId, project.id)];
|
|
20978
21162
|
if (cutoffDate) conditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
20979
21163
|
const rows = app.db.select({
|
|
20980
21164
|
date: gaAiReferrals.date,
|
|
@@ -20997,7 +21181,7 @@ async function ga4Routes(app, opts) {
|
|
|
20997
21181
|
const project = resolveProject(app.db, request.params.name);
|
|
20998
21182
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
20999
21183
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
21000
|
-
const conditions = [
|
|
21184
|
+
const conditions = [eq23(gaSocialReferrals.projectId, project.id)];
|
|
21001
21185
|
if (cutoffDate) conditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
21002
21186
|
const rows = app.db.select({
|
|
21003
21187
|
date: gaSocialReferrals.date,
|
|
@@ -21020,7 +21204,7 @@ async function ga4Routes(app, opts) {
|
|
|
21020
21204
|
return fmt(d);
|
|
21021
21205
|
};
|
|
21022
21206
|
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and16(
|
|
21023
|
-
|
|
21207
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
21024
21208
|
sql9`${gaSocialReferrals.date} >= ${from}`,
|
|
21025
21209
|
sql9`${gaSocialReferrals.date} < ${to}`
|
|
21026
21210
|
)).get();
|
|
@@ -21033,7 +21217,7 @@ async function ga4Routes(app, opts) {
|
|
|
21033
21217
|
source: gaSocialReferrals.source,
|
|
21034
21218
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`
|
|
21035
21219
|
}).from(gaSocialReferrals).where(and16(
|
|
21036
|
-
|
|
21220
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
21037
21221
|
sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
21038
21222
|
sql9`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
21039
21223
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -21041,7 +21225,7 @@ async function ga4Routes(app, opts) {
|
|
|
21041
21225
|
source: gaSocialReferrals.source,
|
|
21042
21226
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`
|
|
21043
21227
|
}).from(gaSocialReferrals).where(and16(
|
|
21044
|
-
|
|
21228
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
21045
21229
|
sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
21046
21230
|
sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
21047
21231
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -21082,16 +21266,16 @@ async function ga4Routes(app, opts) {
|
|
|
21082
21266
|
return fmt(d);
|
|
21083
21267
|
};
|
|
21084
21268
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
21085
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and16(
|
|
21086
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(
|
|
21087
|
-
const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(
|
|
21269
|
+
const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and16(eq23(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
21270
|
+
const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(eq23(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
21271
|
+
const sumDirect = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(eq23(gaTrafficSnapshots.projectId, project.id), sql9`${gaTrafficSnapshots.date} >= ${from}`, sql9`${gaTrafficSnapshots.date} < ${to}`)).get();
|
|
21088
21272
|
const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
|
|
21089
|
-
|
|
21273
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
21090
21274
|
sql9`${gaAiReferrals.date} >= ${from}`,
|
|
21091
21275
|
sql9`${gaAiReferrals.date} < ${to}`,
|
|
21092
|
-
|
|
21276
|
+
eq23(gaAiReferrals.sourceDimension, "session")
|
|
21093
21277
|
)).get();
|
|
21094
|
-
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and16(
|
|
21278
|
+
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and16(eq23(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${from}`, sql9`${gaSocialReferrals.date} < ${to}`)).get();
|
|
21095
21279
|
const todayStr = fmt(today);
|
|
21096
21280
|
const buildTrend = (sum) => {
|
|
21097
21281
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -21101,16 +21285,16 @@ async function ga4Routes(app, opts) {
|
|
|
21101
21285
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
21102
21286
|
};
|
|
21103
21287
|
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
|
|
21104
|
-
|
|
21288
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
21105
21289
|
sql9`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
21106
21290
|
sql9`${gaAiReferrals.date} < ${todayStr}`,
|
|
21107
|
-
|
|
21291
|
+
eq23(gaAiReferrals.sourceDimension, "session")
|
|
21108
21292
|
)).groupBy(gaAiReferrals.source).all();
|
|
21109
21293
|
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
|
|
21110
|
-
|
|
21294
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
21111
21295
|
sql9`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
21112
21296
|
sql9`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
21113
|
-
|
|
21297
|
+
eq23(gaAiReferrals.sourceDimension, "session")
|
|
21114
21298
|
)).groupBy(gaAiReferrals.source).all();
|
|
21115
21299
|
const findBiggestMover = (current, prev) => {
|
|
21116
21300
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
@@ -21126,8 +21310,8 @@ async function ga4Routes(app, opts) {
|
|
|
21126
21310
|
}
|
|
21127
21311
|
return mover;
|
|
21128
21312
|
};
|
|
21129
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(
|
|
21130
|
-
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(
|
|
21313
|
+
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(eq23(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql9`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
|
|
21314
|
+
const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(eq23(gaSocialReferrals.projectId, project.id), sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
|
|
21131
21315
|
return {
|
|
21132
21316
|
total: buildTrend(sumTotal),
|
|
21133
21317
|
organic: buildTrend(sumOrganic),
|
|
@@ -21142,7 +21326,7 @@ async function ga4Routes(app, opts) {
|
|
|
21142
21326
|
const project = resolveProject(app.db, request.params.name);
|
|
21143
21327
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
21144
21328
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
21145
|
-
const conditions = [
|
|
21329
|
+
const conditions = [eq23(gaTrafficSnapshots.projectId, project.id)];
|
|
21146
21330
|
if (cutoffDate) conditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
21147
21331
|
const rows = app.db.select({
|
|
21148
21332
|
date: gaTrafficSnapshots.date,
|
|
@@ -21165,7 +21349,7 @@ async function ga4Routes(app, opts) {
|
|
|
21165
21349
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
21166
21350
|
organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
21167
21351
|
users: sql9`SUM(${gaTrafficSnapshots.users})`
|
|
21168
|
-
}).from(gaTrafficSnapshots).where(
|
|
21352
|
+
}).from(gaTrafficSnapshots).where(eq23(gaTrafficSnapshots.projectId, project.id)).groupBy(sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql9`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
|
|
21169
21353
|
return {
|
|
21170
21354
|
pages: trafficPages.map((r) => ({
|
|
21171
21355
|
landingPage: r.landingPage,
|
|
@@ -21299,7 +21483,7 @@ function parseSchemaPageEntry(entry) {
|
|
|
21299
21483
|
}
|
|
21300
21484
|
|
|
21301
21485
|
// ../integration-wordpress/src/wordpress-client.ts
|
|
21302
|
-
import
|
|
21486
|
+
import crypto20 from "crypto";
|
|
21303
21487
|
function validateUsername(username) {
|
|
21304
21488
|
if (!username || typeof username !== "string" || username.trim().length === 0) {
|
|
21305
21489
|
throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
|
|
@@ -21512,7 +21696,7 @@ function buildSnippet(content) {
|
|
|
21512
21696
|
return `${text2.slice(0, 157)}...`;
|
|
21513
21697
|
}
|
|
21514
21698
|
function contentHash(content) {
|
|
21515
|
-
return
|
|
21699
|
+
return crypto20.createHash("sha256").update(content).digest("hex");
|
|
21516
21700
|
}
|
|
21517
21701
|
function buildAmbiguousSlugMessage(slug, pages) {
|
|
21518
21702
|
const candidates = pages.map((page) => {
|
|
@@ -22801,8 +22985,8 @@ async function wordpressRoutes(app, opts) {
|
|
|
22801
22985
|
}
|
|
22802
22986
|
|
|
22803
22987
|
// ../api-routes/src/backlinks.ts
|
|
22804
|
-
import
|
|
22805
|
-
import { and as and18, asc as asc2, desc as
|
|
22988
|
+
import crypto21 from "crypto";
|
|
22989
|
+
import { and as and18, asc as asc2, desc as desc13, eq as eq24, sql as sql10 } from "drizzle-orm";
|
|
22806
22990
|
|
|
22807
22991
|
// ../integration-commoncrawl/src/constants.ts
|
|
22808
22992
|
import os2 from "os";
|
|
@@ -23289,8 +23473,8 @@ function mapRunRow(row) {
|
|
|
23289
23473
|
};
|
|
23290
23474
|
}
|
|
23291
23475
|
function latestSummaryForProject(db, projectId, release) {
|
|
23292
|
-
const condition = release ? and18(
|
|
23293
|
-
return db.select().from(backlinkSummaries).where(condition).orderBy(
|
|
23476
|
+
const condition = release ? and18(eq24(backlinkSummaries.projectId, projectId), eq24(backlinkSummaries.release, release)) : eq24(backlinkSummaries.projectId, projectId);
|
|
23477
|
+
return db.select().from(backlinkSummaries).where(condition).orderBy(desc13(backlinkSummaries.queriedAt)).limit(1).get();
|
|
23294
23478
|
}
|
|
23295
23479
|
function parseExcludeCrawlers(value) {
|
|
23296
23480
|
if (!value) return false;
|
|
@@ -23299,8 +23483,8 @@ function parseExcludeCrawlers(value) {
|
|
|
23299
23483
|
}
|
|
23300
23484
|
function computeFilteredSummary(db, base) {
|
|
23301
23485
|
const baseDomainCondition = and18(
|
|
23302
|
-
|
|
23303
|
-
|
|
23486
|
+
eq24(backlinkDomains.projectId, base.projectId),
|
|
23487
|
+
eq24(backlinkDomains.release, base.release)
|
|
23304
23488
|
);
|
|
23305
23489
|
const filteredCondition = and18(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
23306
23490
|
const unfilteredAgg = db.select({
|
|
@@ -23311,7 +23495,7 @@ function computeFilteredSummary(db, base) {
|
|
|
23311
23495
|
count: sql10`count(*)`,
|
|
23312
23496
|
total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
23313
23497
|
}).from(backlinkDomains).where(filteredCondition).get();
|
|
23314
|
-
const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(
|
|
23498
|
+
const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc13(backlinkDomains.numHosts)).limit(10).all();
|
|
23315
23499
|
const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
|
|
23316
23500
|
const totalHosts = Number(filteredAgg?.total ?? 0);
|
|
23317
23501
|
const unfilteredLinkingDomains = Number(unfilteredAgg?.count ?? 0);
|
|
@@ -23371,7 +23555,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23371
23555
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
23372
23556
|
);
|
|
23373
23557
|
}
|
|
23374
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
23558
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.release, release)).get();
|
|
23375
23559
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23376
23560
|
if (existing) {
|
|
23377
23561
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -23382,12 +23566,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
23382
23566
|
phaseDetail: null,
|
|
23383
23567
|
error: null,
|
|
23384
23568
|
updatedAt: now
|
|
23385
|
-
}).where(
|
|
23569
|
+
}).where(eq24(ccReleaseSyncs.id, existing.id)).run();
|
|
23386
23570
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
23387
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
23571
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.id, existing.id)).get();
|
|
23388
23572
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
23389
23573
|
}
|
|
23390
|
-
const id =
|
|
23574
|
+
const id = crypto21.randomUUID();
|
|
23391
23575
|
app.db.insert(ccReleaseSyncs).values({
|
|
23392
23576
|
id,
|
|
23393
23577
|
release,
|
|
@@ -23396,15 +23580,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
23396
23580
|
updatedAt: now
|
|
23397
23581
|
}).run();
|
|
23398
23582
|
opts.onReleaseSyncRequested(id, release);
|
|
23399
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
23583
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.id, id)).get();
|
|
23400
23584
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
23401
23585
|
});
|
|
23402
23586
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
23403
|
-
const row = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
23587
|
+
const row = app.db.select().from(ccReleaseSyncs).orderBy(desc13(ccReleaseSyncs.updatedAt)).limit(1).get();
|
|
23404
23588
|
return reply.send(row ? mapSyncRow(row) : null);
|
|
23405
23589
|
});
|
|
23406
23590
|
app.get("/backlinks/syncs", async (_request, reply) => {
|
|
23407
|
-
const rows = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
23591
|
+
const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc13(ccReleaseSyncs.updatedAt)).all();
|
|
23408
23592
|
return reply.send(rows.map(mapSyncRow));
|
|
23409
23593
|
});
|
|
23410
23594
|
app.get("/backlinks/releases", async (_request, reply) => {
|
|
@@ -23444,7 +23628,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23444
23628
|
throw validationError("Invalid release id");
|
|
23445
23629
|
}
|
|
23446
23630
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23447
|
-
const runId =
|
|
23631
|
+
const runId = crypto21.randomUUID();
|
|
23448
23632
|
app.db.insert(runs).values({
|
|
23449
23633
|
id: runId,
|
|
23450
23634
|
projectId: project.id,
|
|
@@ -23454,7 +23638,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23454
23638
|
createdAt: now
|
|
23455
23639
|
}).run();
|
|
23456
23640
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
23457
|
-
const run = app.db.select().from(runs).where(
|
|
23641
|
+
const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
|
|
23458
23642
|
return reply.status(201).send(mapRunRow(run));
|
|
23459
23643
|
});
|
|
23460
23644
|
app.get(
|
|
@@ -23479,15 +23663,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
23479
23663
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
23480
23664
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
23481
23665
|
const baseDomainCondition = and18(
|
|
23482
|
-
|
|
23483
|
-
|
|
23666
|
+
eq24(backlinkDomains.projectId, project.id),
|
|
23667
|
+
eq24(backlinkDomains.release, targetRelease)
|
|
23484
23668
|
);
|
|
23485
23669
|
const domainCondition = excludeCrawlers ? and18(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
23486
23670
|
const totalRow = app.db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
23487
23671
|
const rows = app.db.select({
|
|
23488
23672
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
23489
23673
|
numHosts: backlinkDomains.numHosts
|
|
23490
|
-
}).from(backlinkDomains).where(domainCondition).orderBy(
|
|
23674
|
+
}).from(backlinkDomains).where(domainCondition).orderBy(desc13(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
|
|
23491
23675
|
let summary = null;
|
|
23492
23676
|
if (summaryRow) {
|
|
23493
23677
|
summary = excludeCrawlers ? computeFilteredSummary(app.db, summaryRow) : mapSummaryRow(summaryRow);
|
|
@@ -23503,7 +23687,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23503
23687
|
"/projects/:name/backlinks/history",
|
|
23504
23688
|
async (request, reply) => {
|
|
23505
23689
|
const project = resolveProject(app.db, request.params.name);
|
|
23506
|
-
const rows = app.db.select().from(backlinkSummaries).where(
|
|
23690
|
+
const rows = app.db.select().from(backlinkSummaries).where(eq24(backlinkSummaries.projectId, project.id)).orderBy(asc2(backlinkSummaries.queriedAt)).all();
|
|
23507
23691
|
const response = rows.map((r) => ({
|
|
23508
23692
|
release: r.release,
|
|
23509
23693
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -23517,12 +23701,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
23517
23701
|
}
|
|
23518
23702
|
|
|
23519
23703
|
// ../api-routes/src/traffic.ts
|
|
23520
|
-
import
|
|
23704
|
+
import crypto23 from "crypto";
|
|
23521
23705
|
import { Agent as UndiciAgent } from "undici";
|
|
23522
|
-
import { and as and19, desc as
|
|
23706
|
+
import { and as and19, desc as desc14, eq as eq25, gte as gte3, lte as lte2, sql as sql11 } from "drizzle-orm";
|
|
23523
23707
|
|
|
23524
23708
|
// ../integration-cloud-run/src/auth.ts
|
|
23525
|
-
import
|
|
23709
|
+
import crypto22 from "crypto";
|
|
23526
23710
|
var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
|
|
23527
23711
|
var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
|
|
23528
23712
|
var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -23551,7 +23735,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
|
|
|
23551
23735
|
const headerB64 = encode(header);
|
|
23552
23736
|
const payloadB64 = encode(payload);
|
|
23553
23737
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
23554
|
-
const sign =
|
|
23738
|
+
const sign = crypto22.createSign("RSA-SHA256");
|
|
23555
23739
|
sign.update(signingInput);
|
|
23556
23740
|
const signature = sign.sign(privateKey, "base64url");
|
|
23557
23741
|
return `${signingInput}.${signature}`;
|
|
@@ -27312,8 +27496,8 @@ async function runBackfillTask(options) {
|
|
|
27312
27496
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27313
27497
|
try {
|
|
27314
27498
|
app.db.transaction((tx) => {
|
|
27315
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
27316
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
27499
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq25(runs.id, runId)).run();
|
|
27500
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq25(trafficSources.id, sourceRow.id)).run();
|
|
27317
27501
|
});
|
|
27318
27502
|
} catch {
|
|
27319
27503
|
}
|
|
@@ -27328,7 +27512,7 @@ async function runBackfillTask(options) {
|
|
|
27328
27512
|
if (allEvents.length === 0) {
|
|
27329
27513
|
const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
27330
27514
|
try {
|
|
27331
|
-
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(
|
|
27515
|
+
app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq25(runs.id, runId)).run();
|
|
27332
27516
|
} catch {
|
|
27333
27517
|
}
|
|
27334
27518
|
return;
|
|
@@ -27351,28 +27535,28 @@ async function runBackfillTask(options) {
|
|
|
27351
27535
|
app.db.transaction((tx) => {
|
|
27352
27536
|
tx.delete(crawlerEventsHourly).where(
|
|
27353
27537
|
and19(
|
|
27354
|
-
|
|
27538
|
+
eq25(crawlerEventsHourly.sourceId, sourceRow.id),
|
|
27355
27539
|
gte3(crawlerEventsHourly.tsHour, windowStartIso),
|
|
27356
27540
|
lte2(crawlerEventsHourly.tsHour, windowEndIso)
|
|
27357
27541
|
)
|
|
27358
27542
|
).run();
|
|
27359
27543
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
27360
27544
|
and19(
|
|
27361
|
-
|
|
27545
|
+
eq25(aiUserFetchEventsHourly.sourceId, sourceRow.id),
|
|
27362
27546
|
gte3(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
27363
27547
|
lte2(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
27364
27548
|
)
|
|
27365
27549
|
).run();
|
|
27366
27550
|
tx.delete(aiReferralEventsHourly).where(
|
|
27367
27551
|
and19(
|
|
27368
|
-
|
|
27552
|
+
eq25(aiReferralEventsHourly.sourceId, sourceRow.id),
|
|
27369
27553
|
gte3(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
27370
27554
|
lte2(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
27371
27555
|
)
|
|
27372
27556
|
).run();
|
|
27373
27557
|
tx.delete(rawEventSamples).where(
|
|
27374
27558
|
and19(
|
|
27375
|
-
|
|
27559
|
+
eq25(rawEventSamples.sourceId, sourceRow.id),
|
|
27376
27560
|
gte3(rawEventSamples.ts, windowStartIso),
|
|
27377
27561
|
lte2(rawEventSamples.ts, windowEndIso)
|
|
27378
27562
|
)
|
|
@@ -27437,7 +27621,7 @@ async function runBackfillTask(options) {
|
|
|
27437
27621
|
}
|
|
27438
27622
|
})();
|
|
27439
27623
|
tx.insert(rawEventSamples).values({
|
|
27440
|
-
id:
|
|
27624
|
+
id: crypto23.randomUUID(),
|
|
27441
27625
|
projectId: project.id,
|
|
27442
27626
|
sourceId: sourceRow.id,
|
|
27443
27627
|
ts: sample.observedAt,
|
|
@@ -27461,8 +27645,8 @@ async function runBackfillTask(options) {
|
|
|
27461
27645
|
lastError: null,
|
|
27462
27646
|
lastEventIds: newRingBuffer,
|
|
27463
27647
|
updatedAt: finishedAt
|
|
27464
|
-
}).where(
|
|
27465
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
27648
|
+
}).where(eq25(trafficSources.id, sourceRow.id)).run();
|
|
27649
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq25(runs.id, runId)).run();
|
|
27466
27650
|
});
|
|
27467
27651
|
} catch (e) {
|
|
27468
27652
|
markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -27543,7 +27727,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27543
27727
|
createdAt: existing?.createdAt ?? now,
|
|
27544
27728
|
updatedAt: now
|
|
27545
27729
|
});
|
|
27546
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
27730
|
+
const activeSource = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
|
|
27547
27731
|
const config = {
|
|
27548
27732
|
gcpProjectId,
|
|
27549
27733
|
serviceName: serviceName ?? null,
|
|
@@ -27559,10 +27743,10 @@ async function trafficRoutes(app, opts) {
|
|
|
27559
27743
|
lastError: null,
|
|
27560
27744
|
configJson: config,
|
|
27561
27745
|
updatedAt: now
|
|
27562
|
-
}).where(
|
|
27563
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27746
|
+
}).where(eq25(trafficSources.id, activeSource.id)).run();
|
|
27747
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, activeSource.id)).get();
|
|
27564
27748
|
} else {
|
|
27565
|
-
const newId =
|
|
27749
|
+
const newId = crypto23.randomUUID();
|
|
27566
27750
|
app.db.insert(trafficSources).values({
|
|
27567
27751
|
id: newId,
|
|
27568
27752
|
projectId: project.id,
|
|
@@ -27577,7 +27761,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27577
27761
|
createdAt: now,
|
|
27578
27762
|
updatedAt: now
|
|
27579
27763
|
}).run();
|
|
27580
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27764
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
|
|
27581
27765
|
}
|
|
27582
27766
|
writeAuditLog(app.db, {
|
|
27583
27767
|
projectId: project.id,
|
|
@@ -27629,7 +27813,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27629
27813
|
createdAt: existing?.createdAt ?? now,
|
|
27630
27814
|
updatedAt: now
|
|
27631
27815
|
});
|
|
27632
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
27816
|
+
const activeSource = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
|
|
27633
27817
|
const config = { baseUrl, username };
|
|
27634
27818
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
27635
27819
|
let sourceRow;
|
|
@@ -27640,10 +27824,10 @@ async function trafficRoutes(app, opts) {
|
|
|
27640
27824
|
lastError: null,
|
|
27641
27825
|
configJson: config,
|
|
27642
27826
|
updatedAt: now
|
|
27643
|
-
}).where(
|
|
27644
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27827
|
+
}).where(eq25(trafficSources.id, activeSource.id)).run();
|
|
27828
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, activeSource.id)).get();
|
|
27645
27829
|
} else {
|
|
27646
|
-
const newId =
|
|
27830
|
+
const newId = crypto23.randomUUID();
|
|
27647
27831
|
app.db.insert(trafficSources).values({
|
|
27648
27832
|
id: newId,
|
|
27649
27833
|
projectId: project.id,
|
|
@@ -27658,7 +27842,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27658
27842
|
createdAt: now,
|
|
27659
27843
|
updatedAt: now
|
|
27660
27844
|
}).run();
|
|
27661
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27845
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
|
|
27662
27846
|
}
|
|
27663
27847
|
writeAuditLog(app.db, {
|
|
27664
27848
|
projectId: project.id,
|
|
@@ -27712,7 +27896,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27712
27896
|
createdAt: existing?.createdAt ?? now,
|
|
27713
27897
|
updatedAt: now
|
|
27714
27898
|
});
|
|
27715
|
-
const activeSource = app.db.select().from(trafficSources).where(
|
|
27899
|
+
const activeSource = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
|
|
27716
27900
|
const config = { projectId, teamId, environment };
|
|
27717
27901
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
27718
27902
|
let sourceRow;
|
|
@@ -27723,10 +27907,10 @@ async function trafficRoutes(app, opts) {
|
|
|
27723
27907
|
lastError: null,
|
|
27724
27908
|
configJson: config,
|
|
27725
27909
|
updatedAt: now
|
|
27726
|
-
}).where(
|
|
27727
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27910
|
+
}).where(eq25(trafficSources.id, activeSource.id)).run();
|
|
27911
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, activeSource.id)).get();
|
|
27728
27912
|
} else {
|
|
27729
|
-
const newId =
|
|
27913
|
+
const newId = crypto23.randomUUID();
|
|
27730
27914
|
app.db.insert(trafficSources).values({
|
|
27731
27915
|
id: newId,
|
|
27732
27916
|
projectId: project.id,
|
|
@@ -27749,7 +27933,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27749
27933
|
createdAt: now,
|
|
27750
27934
|
updatedAt: now
|
|
27751
27935
|
}).run();
|
|
27752
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27936
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
|
|
27753
27937
|
}
|
|
27754
27938
|
writeAuditLog(app.db, {
|
|
27755
27939
|
projectId: project.id,
|
|
@@ -27762,7 +27946,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27762
27946
|
});
|
|
27763
27947
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
27764
27948
|
const project = resolveProject(app.db, request.params.name);
|
|
27765
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
27949
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
|
|
27766
27950
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
27767
27951
|
throw notFound("Traffic source", request.params.id);
|
|
27768
27952
|
}
|
|
@@ -27774,7 +27958,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27774
27958
|
const windowEnd = /* @__PURE__ */ new Date();
|
|
27775
27959
|
const startedAt = windowEnd.toISOString();
|
|
27776
27960
|
const syncStartedAtMs = windowEnd.getTime();
|
|
27777
|
-
const runId =
|
|
27961
|
+
const runId = crypto23.randomUUID();
|
|
27778
27962
|
app.db.insert(runs).values({
|
|
27779
27963
|
id: runId,
|
|
27780
27964
|
projectId: project.id,
|
|
@@ -27788,8 +27972,8 @@ async function trafficRoutes(app, opts) {
|
|
|
27788
27972
|
const markFailed = (msg, errorCode) => {
|
|
27789
27973
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27790
27974
|
app.db.transaction((tx) => {
|
|
27791
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
27792
|
-
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(
|
|
27975
|
+
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq25(runs.id, runId)).run();
|
|
27976
|
+
tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq25(trafficSources.id, sourceRow.id)).run();
|
|
27793
27977
|
});
|
|
27794
27978
|
try {
|
|
27795
27979
|
opts.onTrafficSynced?.({
|
|
@@ -27869,7 +28053,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27869
28053
|
}
|
|
27870
28054
|
const credential = credentialStore.getConnection(project.name);
|
|
27871
28055
|
if (!credential) {
|
|
27872
|
-
app.db.delete(runs).where(
|
|
28056
|
+
app.db.delete(runs).where(eq25(runs.id, runId)).run();
|
|
27873
28057
|
throw validationError(
|
|
27874
28058
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
27875
28059
|
);
|
|
@@ -27918,12 +28102,12 @@ async function trafficRoutes(app, opts) {
|
|
|
27918
28102
|
auditAction = "traffic.vercel.synced";
|
|
27919
28103
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
27920
28104
|
if (!credentialStore) {
|
|
27921
|
-
app.db.delete(runs).where(
|
|
28105
|
+
app.db.delete(runs).where(eq25(runs.id, runId)).run();
|
|
27922
28106
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
27923
28107
|
}
|
|
27924
28108
|
const credential = credentialStore.getConnection(project.name);
|
|
27925
28109
|
if (!credential) {
|
|
27926
|
-
app.db.delete(runs).where(
|
|
28110
|
+
app.db.delete(runs).where(eq25(runs.id, runId)).run();
|
|
27927
28111
|
throw validationError(
|
|
27928
28112
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
27929
28113
|
);
|
|
@@ -27983,7 +28167,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27983
28167
|
let aiReferralHitsCount = 0;
|
|
27984
28168
|
let unknownHitsCount = 0;
|
|
27985
28169
|
app.db.transaction((tx) => {
|
|
27986
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
28170
|
+
const latestRow = tx.select().from(trafficSources).where(eq25(trafficSources.id, sourceRow.id)).get();
|
|
27987
28171
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
27988
28172
|
const seenEventIds = new Set(previousIds);
|
|
27989
28173
|
const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
|
|
@@ -28116,7 +28300,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28116
28300
|
}
|
|
28117
28301
|
})();
|
|
28118
28302
|
tx.insert(rawEventSamples).values({
|
|
28119
|
-
id:
|
|
28303
|
+
id: crypto23.randomUUID(),
|
|
28120
28304
|
projectId: project.id,
|
|
28121
28305
|
sourceId: sourceRow.id,
|
|
28122
28306
|
ts: sample.observedAt,
|
|
@@ -28149,8 +28333,8 @@ async function trafficRoutes(app, opts) {
|
|
|
28149
28333
|
if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
|
|
28150
28334
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
28151
28335
|
}
|
|
28152
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
28153
|
-
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(
|
|
28336
|
+
tx.update(trafficSources).set(sourceUpdate).where(eq25(trafficSources.id, sourceRow.id)).run();
|
|
28337
|
+
tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq25(runs.id, runId)).run();
|
|
28154
28338
|
});
|
|
28155
28339
|
writeAuditLog(app.db, {
|
|
28156
28340
|
projectId: project.id,
|
|
@@ -28200,7 +28384,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28200
28384
|
});
|
|
28201
28385
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
|
|
28202
28386
|
const project = resolveProject(app.db, request.params.name);
|
|
28203
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
28387
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
|
|
28204
28388
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
28205
28389
|
throw notFound("Traffic source", request.params.id);
|
|
28206
28390
|
}
|
|
@@ -28350,7 +28534,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28350
28534
|
};
|
|
28351
28535
|
}
|
|
28352
28536
|
const startedAt = windowEnd.toISOString();
|
|
28353
|
-
const runId =
|
|
28537
|
+
const runId = crypto23.randomUUID();
|
|
28354
28538
|
app.db.insert(runs).values({
|
|
28355
28539
|
id: runId,
|
|
28356
28540
|
projectId: project.id,
|
|
@@ -28386,35 +28570,35 @@ async function trafficRoutes(app, opts) {
|
|
|
28386
28570
|
function buildSourceDetail(projectId, row, since) {
|
|
28387
28571
|
const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
28388
28572
|
and19(
|
|
28389
|
-
|
|
28573
|
+
eq25(crawlerEventsHourly.sourceId, row.id),
|
|
28390
28574
|
gte3(crawlerEventsHourly.tsHour, since)
|
|
28391
28575
|
)
|
|
28392
28576
|
).get();
|
|
28393
28577
|
const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
28394
28578
|
and19(
|
|
28395
|
-
|
|
28579
|
+
eq25(aiUserFetchEventsHourly.sourceId, row.id),
|
|
28396
28580
|
gte3(aiUserFetchEventsHourly.tsHour, since)
|
|
28397
28581
|
)
|
|
28398
28582
|
).get();
|
|
28399
28583
|
const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
28400
28584
|
and19(
|
|
28401
|
-
|
|
28585
|
+
eq25(aiReferralEventsHourly.sourceId, row.id),
|
|
28402
28586
|
gte3(aiReferralEventsHourly.tsHour, since)
|
|
28403
28587
|
)
|
|
28404
28588
|
).get();
|
|
28405
28589
|
const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
|
|
28406
28590
|
and19(
|
|
28407
|
-
|
|
28591
|
+
eq25(rawEventSamples.sourceId, row.id),
|
|
28408
28592
|
gte3(rawEventSamples.ts, since)
|
|
28409
28593
|
)
|
|
28410
28594
|
).get();
|
|
28411
28595
|
const latestRun = app.db.select().from(runs).where(
|
|
28412
28596
|
and19(
|
|
28413
|
-
|
|
28414
|
-
|
|
28415
|
-
|
|
28597
|
+
eq25(runs.projectId, projectId),
|
|
28598
|
+
eq25(runs.kind, RunKinds["traffic-sync"]),
|
|
28599
|
+
eq25(runs.sourceId, row.id)
|
|
28416
28600
|
)
|
|
28417
|
-
).orderBy(
|
|
28601
|
+
).orderBy(desc14(runs.startedAt)).limit(1).get();
|
|
28418
28602
|
return {
|
|
28419
28603
|
...rowToDto(row),
|
|
28420
28604
|
totals24h: {
|
|
@@ -28440,7 +28624,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28440
28624
|
"`advanceToNow` must be `true`. There is no implicit reset."
|
|
28441
28625
|
);
|
|
28442
28626
|
}
|
|
28443
|
-
const sourceRow = app.db.select().from(trafficSources).where(and19(
|
|
28627
|
+
const sourceRow = app.db.select().from(trafficSources).where(and19(eq25(trafficSources.projectId, project.id), eq25(trafficSources.id, request.params.id))).get();
|
|
28444
28628
|
if (!sourceRow) {
|
|
28445
28629
|
throw notFound("traffic source", request.params.id);
|
|
28446
28630
|
}
|
|
@@ -28457,7 +28641,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28457
28641
|
status: TrafficSourceStatuses.connected,
|
|
28458
28642
|
lastError: null,
|
|
28459
28643
|
updatedAt: now
|
|
28460
|
-
}).where(
|
|
28644
|
+
}).where(eq25(trafficSources.id, sourceRow.id)).run();
|
|
28461
28645
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
28462
28646
|
projectId: project.id,
|
|
28463
28647
|
actor: "api",
|
|
@@ -28465,20 +28649,20 @@ async function trafficRoutes(app, opts) {
|
|
|
28465
28649
|
entityType: "traffic_source",
|
|
28466
28650
|
entityId: sourceRow.id
|
|
28467
28651
|
}));
|
|
28468
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
28652
|
+
updatedRow = tx.select().from(trafficSources).where(eq25(trafficSources.id, sourceRow.id)).get();
|
|
28469
28653
|
});
|
|
28470
28654
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
28471
28655
|
});
|
|
28472
28656
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
28473
28657
|
const project = resolveProject(app.db, request.params.name);
|
|
28474
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
28658
|
+
const rows = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
|
|
28475
28659
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
28476
28660
|
const response = { sources };
|
|
28477
28661
|
return response;
|
|
28478
28662
|
});
|
|
28479
28663
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
28480
28664
|
const project = resolveProject(app.db, request.params.name);
|
|
28481
|
-
const rows = app.db.select().from(trafficSources).where(
|
|
28665
|
+
const rows = app.db.select().from(trafficSources).where(eq25(trafficSources.projectId, project.id)).orderBy(desc14(trafficSources.createdAt)).all();
|
|
28482
28666
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
28483
28667
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
28484
28668
|
const response = { sources };
|
|
@@ -28488,7 +28672,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28488
28672
|
"/projects/:name/traffic/sources/:id",
|
|
28489
28673
|
async (request) => {
|
|
28490
28674
|
const project = resolveProject(app.db, request.params.name);
|
|
28491
|
-
const row = app.db.select().from(trafficSources).where(
|
|
28675
|
+
const row = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
|
|
28492
28676
|
if (!row || row.projectId !== project.id) {
|
|
28493
28677
|
throw notFound("Traffic source", request.params.id);
|
|
28494
28678
|
}
|
|
@@ -28539,15 +28723,15 @@ async function trafficRoutes(app, opts) {
|
|
|
28539
28723
|
let aiReferralTotal = 0;
|
|
28540
28724
|
if (kind === "all" || kind === TrafficEventKinds.crawler) {
|
|
28541
28725
|
const crawlerFilters = [
|
|
28542
|
-
|
|
28726
|
+
eq25(crawlerEventsHourly.projectId, project.id),
|
|
28543
28727
|
gte3(crawlerEventsHourly.tsHour, sinceIso),
|
|
28544
28728
|
lte2(crawlerEventsHourly.tsHour, untilIso)
|
|
28545
28729
|
];
|
|
28546
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
28730
|
+
if (sourceIdParam) crawlerFilters.push(eq25(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
28547
28731
|
const crawlerWhere = and19(...crawlerFilters);
|
|
28548
28732
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
|
|
28549
28733
|
crawlerTotal = Number(total?.total ?? 0);
|
|
28550
|
-
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(
|
|
28734
|
+
const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc14(crawlerEventsHourly.tsHour)).limit(limit).all();
|
|
28551
28735
|
for (const r of rows) {
|
|
28552
28736
|
events.push({
|
|
28553
28737
|
kind: TrafficEventKinds.crawler,
|
|
@@ -28564,15 +28748,15 @@ async function trafficRoutes(app, opts) {
|
|
|
28564
28748
|
}
|
|
28565
28749
|
if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
|
|
28566
28750
|
const userFetchFilters = [
|
|
28567
|
-
|
|
28751
|
+
eq25(aiUserFetchEventsHourly.projectId, project.id),
|
|
28568
28752
|
gte3(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
28569
28753
|
lte2(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
28570
28754
|
];
|
|
28571
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
28755
|
+
if (sourceIdParam) userFetchFilters.push(eq25(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
28572
28756
|
const userFetchWhere = and19(...userFetchFilters);
|
|
28573
28757
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
|
|
28574
28758
|
aiUserFetchTotal = Number(total?.total ?? 0);
|
|
28575
|
-
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(
|
|
28759
|
+
const rows = app.db.select().from(aiUserFetchEventsHourly).where(userFetchWhere).orderBy(desc14(aiUserFetchEventsHourly.tsHour)).limit(limit).all();
|
|
28576
28760
|
for (const r of rows) {
|
|
28577
28761
|
events.push({
|
|
28578
28762
|
kind: TrafficEventKinds["ai-user-fetch"],
|
|
@@ -28589,15 +28773,15 @@ async function trafficRoutes(app, opts) {
|
|
|
28589
28773
|
}
|
|
28590
28774
|
if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
|
|
28591
28775
|
const aiFilters = [
|
|
28592
|
-
|
|
28776
|
+
eq25(aiReferralEventsHourly.projectId, project.id),
|
|
28593
28777
|
gte3(aiReferralEventsHourly.tsHour, sinceIso),
|
|
28594
28778
|
lte2(aiReferralEventsHourly.tsHour, untilIso)
|
|
28595
28779
|
];
|
|
28596
|
-
if (sourceIdParam) aiFilters.push(
|
|
28780
|
+
if (sourceIdParam) aiFilters.push(eq25(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
28597
28781
|
const aiWhere = and19(...aiFilters);
|
|
28598
28782
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
|
|
28599
28783
|
aiReferralTotal = Number(total?.total ?? 0);
|
|
28600
|
-
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(
|
|
28784
|
+
const rows = app.db.select().from(aiReferralEventsHourly).where(aiWhere).orderBy(desc14(aiReferralEventsHourly.tsHour)).limit(limit).all();
|
|
28601
28785
|
for (const r of rows) {
|
|
28602
28786
|
events.push({
|
|
28603
28787
|
kind: TrafficEventKinds["ai-referral"],
|
|
@@ -28630,7 +28814,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28630
28814
|
}
|
|
28631
28815
|
|
|
28632
28816
|
// ../api-routes/src/doctor/checks/agent.ts
|
|
28633
|
-
import
|
|
28817
|
+
import crypto24 from "crypto";
|
|
28634
28818
|
import fs6 from "fs";
|
|
28635
28819
|
import path7 from "path";
|
|
28636
28820
|
var REQUIRED_SKILLS = ["canonry", "aero"];
|
|
@@ -28783,7 +28967,7 @@ function isInstalled(dir) {
|
|
|
28783
28967
|
}
|
|
28784
28968
|
function hashInstalledFile(filePath) {
|
|
28785
28969
|
try {
|
|
28786
|
-
return
|
|
28970
|
+
return crypto24.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
|
|
28787
28971
|
} catch {
|
|
28788
28972
|
return void 0;
|
|
28789
28973
|
}
|
|
@@ -29085,7 +29269,7 @@ var ga4ConnectionCheck = {
|
|
|
29085
29269
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
29086
29270
|
|
|
29087
29271
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
29088
|
-
import { and as and20, eq as
|
|
29272
|
+
import { and as and20, eq as eq26 } from "drizzle-orm";
|
|
29089
29273
|
var RECENT_SYNC_WARN_DAYS = 7;
|
|
29090
29274
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
29091
29275
|
function skippedNoProject() {
|
|
@@ -29318,7 +29502,7 @@ var recentSyncCheck = {
|
|
|
29318
29502
|
title: "GBP recent sync",
|
|
29319
29503
|
run: (ctx) => {
|
|
29320
29504
|
if (!ctx.project) return skippedNoProject();
|
|
29321
|
-
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and20(
|
|
29505
|
+
const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and20(eq26(gbpLocations.projectId, ctx.project.id), eq26(gbpLocations.selected, true))).all();
|
|
29322
29506
|
if (selected.length === 0) {
|
|
29323
29507
|
return {
|
|
29324
29508
|
status: CheckStatuses.skipped,
|
|
@@ -29378,7 +29562,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
29378
29562
|
);
|
|
29379
29563
|
|
|
29380
29564
|
// ../api-routes/src/doctor/checks/places.ts
|
|
29381
|
-
import { eq as
|
|
29565
|
+
import { eq as eq27 } from "drizzle-orm";
|
|
29382
29566
|
var apiKeyCheck = {
|
|
29383
29567
|
id: "gbp.places.api-key",
|
|
29384
29568
|
category: CheckCategories.auth,
|
|
@@ -29423,7 +29607,7 @@ var apiKeyCheck = {
|
|
|
29423
29607
|
details: { tier: cfg.tier }
|
|
29424
29608
|
};
|
|
29425
29609
|
}
|
|
29426
|
-
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(
|
|
29610
|
+
const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq27(gbpLocations.projectId, ctx.project.id)).all();
|
|
29427
29611
|
const selected = rows.filter((r) => r.selected);
|
|
29428
29612
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
29429
29613
|
const details = {
|
|
@@ -29862,7 +30046,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
29862
30046
|
];
|
|
29863
30047
|
|
|
29864
30048
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
29865
|
-
import { and as and21, eq as
|
|
30049
|
+
import { and as and21, eq as eq28, gte as gte4, ne as ne4, sql as sql12 } from "drizzle-orm";
|
|
29866
30050
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
29867
30051
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
29868
30052
|
function skippedNoProject3() {
|
|
@@ -29877,7 +30061,7 @@ function loadProbes(ctx) {
|
|
|
29877
30061
|
if (!ctx.project) return [];
|
|
29878
30062
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
29879
30063
|
and21(
|
|
29880
|
-
|
|
30064
|
+
eq28(trafficSources.projectId, ctx.project.id),
|
|
29881
30065
|
ne4(trafficSources.status, TrafficSourceStatuses.archived)
|
|
29882
30066
|
)
|
|
29883
30067
|
).all();
|
|
@@ -29958,7 +30142,7 @@ var recentDataCheck = {
|
|
|
29958
30142
|
const recentCrawlers = Number(
|
|
29959
30143
|
ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
29960
30144
|
and21(
|
|
29961
|
-
|
|
30145
|
+
eq28(crawlerEventsHourly.projectId, ctx.project.id),
|
|
29962
30146
|
gte4(crawlerEventsHourly.tsHour, warnCutoff)
|
|
29963
30147
|
)
|
|
29964
30148
|
).get()?.total ?? 0
|
|
@@ -29966,7 +30150,7 @@ var recentDataCheck = {
|
|
|
29966
30150
|
const recentReferrals = Number(
|
|
29967
30151
|
ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
29968
30152
|
and21(
|
|
29969
|
-
|
|
30153
|
+
eq28(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
29970
30154
|
gte4(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
29971
30155
|
)
|
|
29972
30156
|
).get()?.total ?? 0
|
|
@@ -29982,7 +30166,7 @@ var recentDataCheck = {
|
|
|
29982
30166
|
const olderCrawlers = Number(
|
|
29983
30167
|
ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
29984
30168
|
and21(
|
|
29985
|
-
|
|
30169
|
+
eq28(crawlerEventsHourly.projectId, ctx.project.id),
|
|
29986
30170
|
gte4(crawlerEventsHourly.tsHour, failCutoff)
|
|
29987
30171
|
)
|
|
29988
30172
|
).get()?.total ?? 0
|
|
@@ -29990,7 +30174,7 @@ var recentDataCheck = {
|
|
|
29990
30174
|
const olderReferrals = Number(
|
|
29991
30175
|
ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
29992
30176
|
and21(
|
|
29993
|
-
|
|
30177
|
+
eq28(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
29994
30178
|
gte4(aiReferralEventsHourly.tsHour, failCutoff)
|
|
29995
30179
|
)
|
|
29996
30180
|
).get()?.total ?? 0
|
|
@@ -30368,8 +30552,8 @@ async function doctorRoutes(app, opts) {
|
|
|
30368
30552
|
}
|
|
30369
30553
|
|
|
30370
30554
|
// ../api-routes/src/discovery/routes.ts
|
|
30371
|
-
import
|
|
30372
|
-
import { and as and22, desc as
|
|
30555
|
+
import crypto25 from "crypto";
|
|
30556
|
+
import { and as and22, desc as desc15, eq as eq29, gte as gte5, inArray as inArray10 } from "drizzle-orm";
|
|
30373
30557
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
30374
30558
|
async function discoveryRoutes(app, opts) {
|
|
30375
30559
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -30402,20 +30586,20 @@ async function discoveryRoutes(app, opts) {
|
|
|
30402
30586
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
30403
30587
|
const decision = app.db.transaction((tx) => {
|
|
30404
30588
|
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and22(
|
|
30405
|
-
|
|
30406
|
-
|
|
30589
|
+
eq29(discoverySessions.projectId, project.id),
|
|
30590
|
+
eq29(discoverySessions.icpDescription, icpDescription),
|
|
30407
30591
|
inArray10(discoverySessions.status, [
|
|
30408
30592
|
DiscoverySessionStatuses.queued,
|
|
30409
30593
|
DiscoverySessionStatuses.seeding,
|
|
30410
30594
|
DiscoverySessionStatuses.probing
|
|
30411
30595
|
]),
|
|
30412
30596
|
gte5(discoverySessions.createdAt, ageFloorIso)
|
|
30413
|
-
)).orderBy(
|
|
30597
|
+
)).orderBy(desc15(discoverySessions.createdAt)).get();
|
|
30414
30598
|
if (existing && existing.runId) {
|
|
30415
30599
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
30416
30600
|
}
|
|
30417
|
-
const sessionId =
|
|
30418
|
-
const runId =
|
|
30601
|
+
const sessionId = crypto25.randomUUID();
|
|
30602
|
+
const runId = crypto25.randomUUID();
|
|
30419
30603
|
tx.insert(discoverySessions).values({
|
|
30420
30604
|
id: sessionId,
|
|
30421
30605
|
projectId: project.id,
|
|
@@ -30473,7 +30657,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30473
30657
|
const project = resolveProject(app.db, request.params.name);
|
|
30474
30658
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
30475
30659
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
30476
|
-
const rows = app.db.select().from(discoverySessions).where(
|
|
30660
|
+
const rows = app.db.select().from(discoverySessions).where(eq29(discoverySessions.projectId, project.id)).orderBy(desc15(discoverySessions.createdAt)).limit(limit).all();
|
|
30477
30661
|
return reply.send(rows.map(serializeSession));
|
|
30478
30662
|
}
|
|
30479
30663
|
);
|
|
@@ -30481,11 +30665,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
30481
30665
|
"/projects/:name/discover/sessions/:id",
|
|
30482
30666
|
async (request, reply) => {
|
|
30483
30667
|
const project = resolveProject(app.db, request.params.name);
|
|
30484
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
30668
|
+
const session = app.db.select().from(discoverySessions).where(eq29(discoverySessions.id, request.params.id)).get();
|
|
30485
30669
|
if (!session || session.projectId !== project.id) {
|
|
30486
30670
|
throw notFound("Discovery session", request.params.id);
|
|
30487
30671
|
}
|
|
30488
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
30672
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq29(discoveryProbes.sessionId, session.id)).all();
|
|
30489
30673
|
const detail = {
|
|
30490
30674
|
...serializeSession(session),
|
|
30491
30675
|
probes: probeRows.map(serializeProbe)
|
|
@@ -30497,12 +30681,12 @@ async function discoveryRoutes(app, opts) {
|
|
|
30497
30681
|
"/projects/:name/discover/sessions/:id/promote",
|
|
30498
30682
|
async (request, reply) => {
|
|
30499
30683
|
const project = resolveProject(app.db, request.params.name);
|
|
30500
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
30684
|
+
const session = app.db.select().from(discoverySessions).where(eq29(discoverySessions.id, request.params.id)).get();
|
|
30501
30685
|
if (!session || session.projectId !== project.id) {
|
|
30502
30686
|
throw notFound("Discovery session", request.params.id);
|
|
30503
30687
|
}
|
|
30504
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
30505
|
-
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
30688
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq29(discoveryProbes.sessionId, session.id)).all();
|
|
30689
|
+
const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq29(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
|
|
30506
30690
|
const seenCompetitors = new Set(existingCompetitors);
|
|
30507
30691
|
const cited = /* @__PURE__ */ new Set();
|
|
30508
30692
|
const aspirational = /* @__PURE__ */ new Set();
|
|
@@ -30531,7 +30715,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30531
30715
|
);
|
|
30532
30716
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
30533
30717
|
const project = resolveProject(app.db, request.params.name);
|
|
30534
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
30718
|
+
const session = app.db.select().from(discoverySessions).where(eq29(discoverySessions.id, request.params.id)).get();
|
|
30535
30719
|
if (!session || session.projectId !== project.id) {
|
|
30536
30720
|
throw notFound("Discovery session", request.params.id);
|
|
30537
30721
|
}
|
|
@@ -30554,7 +30738,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30554
30738
|
const bucketSet = new Set(buckets);
|
|
30555
30739
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
30556
30740
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
30557
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
30741
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq29(discoveryProbes.sessionId, session.id)).all();
|
|
30558
30742
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
30559
30743
|
for (const probe of probeRows) {
|
|
30560
30744
|
if (!probe.bucket) continue;
|
|
@@ -30562,7 +30746,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30562
30746
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
30563
30747
|
}
|
|
30564
30748
|
const existingQueries = new Set(
|
|
30565
|
-
app.db.select({ query: queries.query }).from(queries).where(
|
|
30749
|
+
app.db.select({ query: queries.query }).from(queries).where(eq29(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
|
|
30566
30750
|
);
|
|
30567
30751
|
const promotedQueries = [];
|
|
30568
30752
|
const skippedQueries = [];
|
|
@@ -30578,7 +30762,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30578
30762
|
const skippedCompetitors = [];
|
|
30579
30763
|
if (includeCompetitors) {
|
|
30580
30764
|
const existingCompetitors = new Set(
|
|
30581
|
-
app.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
30765
|
+
app.db.select({ domain: competitors.domain }).from(competitors).where(eq29(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
|
|
30582
30766
|
);
|
|
30583
30767
|
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
30584
30768
|
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
@@ -30597,7 +30781,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30597
30781
|
app.db.transaction((tx) => {
|
|
30598
30782
|
for (const query of promotedQueries) {
|
|
30599
30783
|
tx.insert(queries).values({
|
|
30600
|
-
id:
|
|
30784
|
+
id: crypto25.randomUUID(),
|
|
30601
30785
|
projectId: project.id,
|
|
30602
30786
|
query,
|
|
30603
30787
|
provenance,
|
|
@@ -30606,7 +30790,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30606
30790
|
}
|
|
30607
30791
|
for (const domain of promotedCompetitors) {
|
|
30608
30792
|
tx.insert(competitors).values({
|
|
30609
|
-
id:
|
|
30793
|
+
id: crypto25.randomUUID(),
|
|
30610
30794
|
projectId: project.id,
|
|
30611
30795
|
domain,
|
|
30612
30796
|
provenance,
|
|
@@ -30680,8 +30864,8 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
|
30680
30864
|
}
|
|
30681
30865
|
|
|
30682
30866
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
30683
|
-
import
|
|
30684
|
-
import { eq as
|
|
30867
|
+
import crypto26 from "crypto";
|
|
30868
|
+
import { eq as eq30 } from "drizzle-orm";
|
|
30685
30869
|
var DEFAULT_DEDUP_THRESHOLD = 0.85;
|
|
30686
30870
|
var DEFAULT_MAX_PROBES = 100;
|
|
30687
30871
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
@@ -30736,7 +30920,7 @@ async function executeDiscovery(opts) {
|
|
|
30736
30920
|
status: DiscoverySessionStatuses.seeding,
|
|
30737
30921
|
dedupThreshold,
|
|
30738
30922
|
startedAt
|
|
30739
|
-
}).where(
|
|
30923
|
+
}).where(eq30(discoverySessions.id, opts.sessionId)).run();
|
|
30740
30924
|
const seedResult = await opts.deps.seed({
|
|
30741
30925
|
project: opts.project,
|
|
30742
30926
|
icpDescription: opts.icpDescription,
|
|
@@ -30756,7 +30940,7 @@ async function executeDiscovery(opts) {
|
|
|
30756
30940
|
seedProvider: seedResult.provider,
|
|
30757
30941
|
seedCountRaw,
|
|
30758
30942
|
seedCount
|
|
30759
|
-
}).where(
|
|
30943
|
+
}).where(eq30(discoverySessions.id, opts.sessionId)).run();
|
|
30760
30944
|
const probeRows = [];
|
|
30761
30945
|
const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
|
|
30762
30946
|
for (const query of probedCanonicals) {
|
|
@@ -30769,7 +30953,7 @@ async function executeDiscovery(opts) {
|
|
|
30769
30953
|
probeRows.push({ citedDomains: probe.citedDomains, bucket });
|
|
30770
30954
|
buckets[bucket]++;
|
|
30771
30955
|
opts.db.insert(discoveryProbes).values({
|
|
30772
|
-
id:
|
|
30956
|
+
id: crypto26.randomUUID(),
|
|
30773
30957
|
sessionId: opts.sessionId,
|
|
30774
30958
|
projectId: opts.project.id,
|
|
30775
30959
|
query,
|
|
@@ -30796,7 +30980,7 @@ async function executeDiscovery(opts) {
|
|
|
30796
30980
|
wastedCount: buckets["wasted-surface"],
|
|
30797
30981
|
competitorMap,
|
|
30798
30982
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30799
|
-
}).where(
|
|
30983
|
+
}).where(eq30(discoverySessions.id, opts.sessionId)).run();
|
|
30800
30984
|
return {
|
|
30801
30985
|
buckets,
|
|
30802
30986
|
competitorMap,
|
|
@@ -30810,7 +30994,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
30810
30994
|
status: DiscoverySessionStatuses.failed,
|
|
30811
30995
|
error,
|
|
30812
30996
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30813
|
-
}).where(
|
|
30997
|
+
}).where(eq30(discoverySessions.id, sessionId)).run();
|
|
30814
30998
|
}
|
|
30815
30999
|
function dedupeStrings(input) {
|
|
30816
31000
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -30917,6 +31101,7 @@ async function apiRoutes(app, opts) {
|
|
|
30917
31101
|
bing: opts.bingSettingsSummary,
|
|
30918
31102
|
onBingUpdate: opts.onBingSettingsUpdate
|
|
30919
31103
|
});
|
|
31104
|
+
await api.register(keysRoutes);
|
|
30920
31105
|
await api.register(snapshotRoutes, {
|
|
30921
31106
|
onSnapshotRequested: opts.onSnapshotRequested
|
|
30922
31107
|
});
|
|
@@ -31138,7 +31323,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
31138
31323
|
}
|
|
31139
31324
|
|
|
31140
31325
|
// src/intelligence-service.ts
|
|
31141
|
-
import
|
|
31326
|
+
import crypto27 from "crypto";
|
|
31142
31327
|
|
|
31143
31328
|
// src/logger.ts
|
|
31144
31329
|
var IS_TTY = process.stdout.isTTY === true;
|
|
@@ -31370,15 +31555,15 @@ var IntelligenceService = class {
|
|
|
31370
31555
|
analyzeAndPersist(runId, projectId) {
|
|
31371
31556
|
const recentRuns = this.db.select().from(runs).where(
|
|
31372
31557
|
and23(
|
|
31373
|
-
|
|
31374
|
-
or5(
|
|
31558
|
+
eq31(runs.projectId, projectId),
|
|
31559
|
+
or5(eq31(runs.status, "completed"), eq31(runs.status, "partial")),
|
|
31375
31560
|
// Defensive: RunCoordinator already skips probes before this is
|
|
31376
31561
|
// called, but if a future call site invokes analyzeAndPersist
|
|
31377
31562
|
// directly for a probe, probes still must not pollute the
|
|
31378
31563
|
// intelligence window.
|
|
31379
31564
|
ne5(runs.trigger, RunTriggers.probe)
|
|
31380
31565
|
)
|
|
31381
|
-
).orderBy(
|
|
31566
|
+
).orderBy(desc16(runs.finishedAt), desc16(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
31382
31567
|
if (recentRuns.length === 0) {
|
|
31383
31568
|
log.info("intelligence.skip", { runId, reason: "no completed runs" });
|
|
31384
31569
|
return null;
|
|
@@ -31453,7 +31638,7 @@ var IntelligenceService = class {
|
|
|
31453
31638
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
31454
31639
|
*/
|
|
31455
31640
|
analyzeAndPersistGbp(runId, projectId) {
|
|
31456
|
-
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(
|
|
31641
|
+
const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq31(runs.id, runId)).get();
|
|
31457
31642
|
if (!runRow) {
|
|
31458
31643
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
31459
31644
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -31462,8 +31647,8 @@ var IntelligenceService = class {
|
|
|
31462
31647
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
31463
31648
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
31464
31649
|
const selected = this.db.select().from(gbpLocations).where(and23(
|
|
31465
|
-
|
|
31466
|
-
|
|
31650
|
+
eq31(gbpLocations.projectId, projectId),
|
|
31651
|
+
eq31(gbpLocations.selected, true),
|
|
31467
31652
|
gte6(gbpLocations.syncedAt, windowStart),
|
|
31468
31653
|
lte3(gbpLocations.syncedAt, windowEnd)
|
|
31469
31654
|
)).all();
|
|
@@ -31498,10 +31683,10 @@ var IntelligenceService = class {
|
|
|
31498
31683
|
}
|
|
31499
31684
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
31500
31685
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
31501
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and23(
|
|
31502
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and23(
|
|
31503
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and23(
|
|
31504
|
-
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and23(
|
|
31686
|
+
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and23(eq31(gbpDailyMetrics.projectId, projectId), eq31(gbpDailyMetrics.locationName, locationName))).all();
|
|
31687
|
+
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and23(eq31(gbpPlaceActions.projectId, projectId), eq31(gbpPlaceActions.locationName, locationName))).all();
|
|
31688
|
+
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and23(eq31(gbpLodgingSnapshots.projectId, projectId), eq31(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc16(gbpLodgingSnapshots.syncedAt)).limit(1).get();
|
|
31689
|
+
const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and23(eq31(gbpPlaceDetails.projectId, projectId), eq31(gbpPlaceDetails.locationName, locationName))).orderBy(desc16(gbpPlaceDetails.syncedAt)).limit(1).get();
|
|
31505
31690
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
31506
31691
|
const summary = buildGbpSummary({
|
|
31507
31692
|
locationName,
|
|
@@ -31533,7 +31718,7 @@ var IntelligenceService = class {
|
|
|
31533
31718
|
/** Build the month-over-month keyword series for a location from the
|
|
31534
31719
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
31535
31720
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
31536
|
-
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and23(
|
|
31721
|
+
const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and23(eq31(gbpKeywordMonthly.projectId, projectId), eq31(gbpKeywordMonthly.locationName, locationName))).all();
|
|
31537
31722
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
31538
31723
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
31539
31724
|
const recentMonth = months[0] ?? null;
|
|
@@ -31564,7 +31749,7 @@ var IntelligenceService = class {
|
|
|
31564
31749
|
*/
|
|
31565
31750
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
31566
31751
|
const covered = new Set(coveredLocationNames);
|
|
31567
|
-
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and23(
|
|
31752
|
+
const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and23(eq31(insights.projectId, projectId), eq31(insights.provider, GBP_INSIGHT_PROVIDER))).all();
|
|
31568
31753
|
const staleIds = [];
|
|
31569
31754
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
31570
31755
|
for (const row of existing) {
|
|
@@ -31575,7 +31760,7 @@ var IntelligenceService = class {
|
|
|
31575
31760
|
}
|
|
31576
31761
|
this.db.transaction((tx) => {
|
|
31577
31762
|
for (const id of staleIds) {
|
|
31578
|
-
tx.delete(insights).where(
|
|
31763
|
+
tx.delete(insights).where(eq31(insights.id, id)).run();
|
|
31579
31764
|
}
|
|
31580
31765
|
for (const insight of gbpInsights) {
|
|
31581
31766
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -31653,7 +31838,7 @@ var IntelligenceService = class {
|
|
|
31653
31838
|
* create per run + aggregate). DB is left untouched.
|
|
31654
31839
|
*/
|
|
31655
31840
|
backfill(projectName, opts, onProgress) {
|
|
31656
|
-
const project = this.db.select().from(projects).where(
|
|
31841
|
+
const project = this.db.select().from(projects).where(eq31(projects.name, projectName)).get();
|
|
31657
31842
|
if (!project) {
|
|
31658
31843
|
throw new Error(`Project "${projectName}" not found`);
|
|
31659
31844
|
}
|
|
@@ -31667,8 +31852,8 @@ var IntelligenceService = class {
|
|
|
31667
31852
|
}
|
|
31668
31853
|
const allRuns = this.db.select().from(runs).where(
|
|
31669
31854
|
and23(
|
|
31670
|
-
|
|
31671
|
-
or5(
|
|
31855
|
+
eq31(runs.projectId, project.id),
|
|
31856
|
+
or5(eq31(runs.status, "completed"), eq31(runs.status, "partial")),
|
|
31672
31857
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
31673
31858
|
ne5(runs.trigger, RunTriggers.probe)
|
|
31674
31859
|
)
|
|
@@ -31747,7 +31932,7 @@ var IntelligenceService = class {
|
|
|
31747
31932
|
return { processed, skipped, totalInsights };
|
|
31748
31933
|
}
|
|
31749
31934
|
loadTrackedCompetitors(projectId) {
|
|
31750
|
-
return this.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
31935
|
+
return this.db.select({ domain: competitors.domain }).from(competitors).where(eq31(competitors.projectId, projectId)).all().map((r) => r.domain);
|
|
31751
31936
|
}
|
|
31752
31937
|
/**
|
|
31753
31938
|
* Wipe transition signals from an analysis result while keeping health.
|
|
@@ -31768,15 +31953,15 @@ var IntelligenceService = class {
|
|
|
31768
31953
|
}
|
|
31769
31954
|
persistResult(result, runId, projectId) {
|
|
31770
31955
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
31771
|
-
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(
|
|
31956
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq31(insights.runId, runId)).all();
|
|
31772
31957
|
for (const row of existingInsights) {
|
|
31773
31958
|
if (row.dismissed) {
|
|
31774
31959
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
31775
31960
|
}
|
|
31776
31961
|
}
|
|
31777
31962
|
this.db.transaction((tx) => {
|
|
31778
|
-
tx.delete(insights).where(
|
|
31779
|
-
tx.delete(healthSnapshots).where(
|
|
31963
|
+
tx.delete(insights).where(eq31(insights.runId, runId)).run();
|
|
31964
|
+
tx.delete(healthSnapshots).where(eq31(healthSnapshots.runId, runId)).run();
|
|
31780
31965
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31781
31966
|
for (const insight of result.insights) {
|
|
31782
31967
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -31796,7 +31981,7 @@ var IntelligenceService = class {
|
|
|
31796
31981
|
}).run();
|
|
31797
31982
|
}
|
|
31798
31983
|
tx.insert(healthSnapshots).values({
|
|
31799
|
-
id:
|
|
31984
|
+
id: crypto27.randomUUID(),
|
|
31800
31985
|
projectId,
|
|
31801
31986
|
runId,
|
|
31802
31987
|
overallCitedRate: String(result.health.overallCitedRate),
|
|
@@ -31827,14 +32012,14 @@ var IntelligenceService = class {
|
|
|
31827
32012
|
applySeverityTiering(rawInsights, excludeRunId, projectId) {
|
|
31828
32013
|
const regressions = rawInsights.filter((i) => i.type === "regression");
|
|
31829
32014
|
if (regressions.length === 0) return rawInsights;
|
|
31830
|
-
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(
|
|
32015
|
+
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq31(gscSearchData.projectId, projectId)).all();
|
|
31831
32016
|
const gscConnected = gscRows.length > 0;
|
|
31832
32017
|
const gscImpressionsByQuery = /* @__PURE__ */ new Map();
|
|
31833
32018
|
for (const row of gscRows) {
|
|
31834
32019
|
const key = row.query.toLowerCase();
|
|
31835
32020
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
31836
32021
|
}
|
|
31837
|
-
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
32022
|
+
const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq31(projects.id, projectId)).get();
|
|
31838
32023
|
const locationCount = Math.max(
|
|
31839
32024
|
1,
|
|
31840
32025
|
(projectRow?.locations ?? []).length
|
|
@@ -31842,13 +32027,13 @@ var IntelligenceService = class {
|
|
|
31842
32027
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
31843
32028
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
31844
32029
|
and23(
|
|
31845
|
-
|
|
31846
|
-
|
|
31847
|
-
or5(
|
|
32030
|
+
eq31(runs.projectId, projectId),
|
|
32031
|
+
eq31(runs.kind, RunKinds["answer-visibility"]),
|
|
32032
|
+
or5(eq31(runs.status, "completed"), eq31(runs.status, "partial")),
|
|
31848
32033
|
// Defensive — see top of file.
|
|
31849
32034
|
ne5(runs.trigger, RunTriggers.probe)
|
|
31850
32035
|
)
|
|
31851
|
-
).orderBy(
|
|
32036
|
+
).orderBy(desc16(runs.createdAt), desc16(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
31852
32037
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
31853
32038
|
const recentRunIds = [];
|
|
31854
32039
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -31864,7 +32049,7 @@ var IntelligenceService = class {
|
|
|
31864
32049
|
const haveHistory = recentRunIds.length > 0;
|
|
31865
32050
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
31866
32051
|
if (haveHistory) {
|
|
31867
|
-
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and23(
|
|
32052
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and23(eq31(insights.type, "regression"), inArray11(insights.runId, recentRunIds))).all();
|
|
31868
32053
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
31869
32054
|
for (const row of priorRows) {
|
|
31870
32055
|
if (!row.runId) continue;
|
|
@@ -31893,7 +32078,7 @@ var IntelligenceService = class {
|
|
|
31893
32078
|
});
|
|
31894
32079
|
}
|
|
31895
32080
|
buildRunData(runId, projectId, completedAt, location = null) {
|
|
31896
|
-
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(
|
|
32081
|
+
const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq31(projects.id, projectId)).get();
|
|
31897
32082
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
31898
32083
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
31899
32084
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -31909,7 +32094,7 @@ var IntelligenceService = class {
|
|
|
31909
32094
|
citedDomains: querySnapshots.citedDomains,
|
|
31910
32095
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
31911
32096
|
snapshotLocation: querySnapshots.location
|
|
31912
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
32097
|
+
}).from(querySnapshots).leftJoin(queries, eq31(querySnapshots.queryId, queries.id)).where(eq31(querySnapshots.runId, runId)).all();
|
|
31913
32098
|
const snapshots = [];
|
|
31914
32099
|
let orphanCount = 0;
|
|
31915
32100
|
for (const r of rows) {
|