@ainyc/canonry 4.69.0 → 4.69.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +20 -1
- package/assets/assets/{BacklinksPage-BXVoTc3S.js → BacklinksPage-C4PM9i3H.js} +1 -1
- package/assets/assets/{ChartPrimitives-pJRJPd17.js → ChartPrimitives-nxvyoxCn.js} +1 -1
- package/assets/assets/ProjectPage-C-Xn3lJ1.js +6 -0
- package/assets/assets/{RunRow-DPL4FxPl.js → RunRow-DRncrtMo.js} +1 -1
- package/assets/assets/{RunsPage-B4dCG_66.js → RunsPage-cFFewGZB.js} +1 -1
- package/assets/assets/{SettingsPage-D8aWhLsU.js → SettingsPage-BxL_din4.js} +1 -1
- package/assets/assets/{TrafficPage-COZa5_Q_.js → TrafficPage-CKDv33KP.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-CN8Cx6YI.js → TrafficSourceDetailPage-yMUcgBbw.js} +1 -1
- package/assets/assets/{extract-error-message-D8g8YXDH.js → extract-error-message-D3DfO_Ep.js} +1 -1
- package/assets/assets/{index-DPO3uDWZ.js → index-mVWwxF_h.js} +86 -86
- package/assets/assets/{server-traffic-0JT1Vbj_.js → server-traffic-D4hR4Sy6.js} +1 -1
- package/assets/assets/{trash-2-_1TgguOP.js → trash-2-DmBrLnSR.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-D75O5A27.js → chunk-5FM7QRYD.js} +1558 -1526
- package/dist/{chunk-WQ44ZXGQ.js → chunk-SBYA3LEJ.js} +4 -4
- package/dist/{chunk-B2CH7GBW.js → chunk-WFM2O72W.js} +465 -307
- package/dist/{chunk-YYFBMDLC.js → chunk-ZNWMVYYU.js} +53 -1
- package/dist/cli.js +195 -79
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-IUKD3PMZ.js → intelligence-service-DVVSE3G7.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
- package/assets/assets/ProjectPage-CQ1_itHh.js +0 -6
|
@@ -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,
|
|
@@ -12575,6 +12587,8 @@ function makeSnippet(text2, query) {
|
|
|
12575
12587
|
import { z } from "zod";
|
|
12576
12588
|
var SCHEMA_TABLE = {
|
|
12577
12589
|
AgentProvidersResponseDto: agentProvidersResponseDtoSchema,
|
|
12590
|
+
ApiKeyDto: apiKeyDtoSchema,
|
|
12591
|
+
ApiKeyListDto: apiKeyListDtoSchema,
|
|
12578
12592
|
AuditLogEntry: auditLogEntrySchema,
|
|
12579
12593
|
BacklinkHistoryEntry: backlinkHistoryEntrySchema,
|
|
12580
12594
|
BacklinkListResponse: backlinkListResponseSchema,
|
|
@@ -12602,6 +12616,8 @@ var SCHEMA_TABLE = {
|
|
|
12602
12616
|
ContentTargetDismissalDto: contentTargetDismissalDtoSchema,
|
|
12603
12617
|
ContentTargetDismissalsResponseDto: contentTargetDismissalsResponseDtoSchema,
|
|
12604
12618
|
ContentTargetsResponseDto: contentTargetsResponseDtoSchema,
|
|
12619
|
+
CreateApiKeyRequest: createApiKeyRequestSchema,
|
|
12620
|
+
CreatedApiKeyDto: createdApiKeyDtoSchema,
|
|
12605
12621
|
RecommendationExplanationDto: recommendationExplanationDtoSchema,
|
|
12606
12622
|
DiscoveryPromotePreview: discoveryPromotePreviewSchema,
|
|
12607
12623
|
DiscoveryPromoteResult: discoveryPromoteResultSchema,
|
|
@@ -12769,6 +12785,13 @@ var notificationIdParameter = {
|
|
|
12769
12785
|
description: "Notification ID.",
|
|
12770
12786
|
schema: stringSchema
|
|
12771
12787
|
};
|
|
12788
|
+
var keyIdParameter = {
|
|
12789
|
+
name: "id",
|
|
12790
|
+
in: "path",
|
|
12791
|
+
required: true,
|
|
12792
|
+
description: "API key ID.",
|
|
12793
|
+
schema: stringSchema
|
|
12794
|
+
};
|
|
12772
12795
|
var providerNameParameter = {
|
|
12773
12796
|
name: "name",
|
|
12774
12797
|
in: "path",
|
|
@@ -13692,6 +13715,50 @@ var routeCatalog = [
|
|
|
13692
13715
|
501: errorResponse("Google settings updates are not supported.")
|
|
13693
13716
|
}
|
|
13694
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
|
+
},
|
|
13695
13762
|
{
|
|
13696
13763
|
method: "post",
|
|
13697
13764
|
path: "/api/v1/snapshot",
|
|
@@ -16541,6 +16608,96 @@ async function settingsRoutes(app, opts) {
|
|
|
16541
16608
|
});
|
|
16542
16609
|
}
|
|
16543
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
|
+
|
|
16544
16701
|
// ../api-routes/src/snapshot.ts
|
|
16545
16702
|
async function snapshotRoutes(app, opts) {
|
|
16546
16703
|
app.post("/snapshot", async (request) => {
|
|
@@ -16600,8 +16757,8 @@ async function telemetryRoutes(app, opts) {
|
|
|
16600
16757
|
}
|
|
16601
16758
|
|
|
16602
16759
|
// ../api-routes/src/schedules.ts
|
|
16603
|
-
import
|
|
16604
|
-
import { and as and12, eq as
|
|
16760
|
+
import crypto13 from "crypto";
|
|
16761
|
+
import { and as and12, eq as eq18 } from "drizzle-orm";
|
|
16605
16762
|
function parseKindParam(raw) {
|
|
16606
16763
|
if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
|
|
16607
16764
|
const parsed = schedulableRunKindSchema.safeParse(raw);
|
|
@@ -16628,7 +16785,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
16628
16785
|
if (!sourceId) {
|
|
16629
16786
|
throw validationError('"sourceId" is required when kind is "traffic-sync"');
|
|
16630
16787
|
}
|
|
16631
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
16788
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq18(trafficSources.id, sourceId)).get();
|
|
16632
16789
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
16633
16790
|
throw notFound("Traffic source", sourceId);
|
|
16634
16791
|
}
|
|
@@ -16670,7 +16827,7 @@ async function scheduleRoutes(app, opts) {
|
|
|
16670
16827
|
}
|
|
16671
16828
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16672
16829
|
const enabledBool = enabled !== false;
|
|
16673
|
-
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();
|
|
16674
16831
|
if (existing) {
|
|
16675
16832
|
app.db.update(schedules).set({
|
|
16676
16833
|
cronExpr,
|
|
@@ -16680,10 +16837,10 @@ async function scheduleRoutes(app, opts) {
|
|
|
16680
16837
|
sourceId: sourceId ?? null,
|
|
16681
16838
|
enabled: enabledBool,
|
|
16682
16839
|
updatedAt: now
|
|
16683
|
-
}).where(
|
|
16840
|
+
}).where(eq18(schedules.id, existing.id)).run();
|
|
16684
16841
|
} else {
|
|
16685
16842
|
app.db.insert(schedules).values({
|
|
16686
|
-
id:
|
|
16843
|
+
id: crypto13.randomUUID(),
|
|
16687
16844
|
projectId: project.id,
|
|
16688
16845
|
kind,
|
|
16689
16846
|
cronExpr,
|
|
@@ -16704,13 +16861,13 @@ async function scheduleRoutes(app, opts) {
|
|
|
16704
16861
|
diff: { kind, cronExpr, preset, timezone, providers, sourceId }
|
|
16705
16862
|
});
|
|
16706
16863
|
opts.onScheduleUpdated?.("upsert", project.id, kind);
|
|
16707
|
-
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();
|
|
16708
16865
|
return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
|
|
16709
16866
|
});
|
|
16710
16867
|
app.get("/projects/:name/schedule", async (request, reply) => {
|
|
16711
16868
|
const project = resolveProject(app.db, request.params.name);
|
|
16712
16869
|
const kind = parseKindParam(request.query?.kind);
|
|
16713
|
-
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();
|
|
16714
16871
|
if (!schedule) {
|
|
16715
16872
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
16716
16873
|
}
|
|
@@ -16719,11 +16876,11 @@ async function scheduleRoutes(app, opts) {
|
|
|
16719
16876
|
app.delete("/projects/:name/schedule", async (request, reply) => {
|
|
16720
16877
|
const project = resolveProject(app.db, request.params.name);
|
|
16721
16878
|
const kind = parseKindParam(request.query?.kind);
|
|
16722
|
-
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();
|
|
16723
16880
|
if (!schedule) {
|
|
16724
16881
|
throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
|
|
16725
16882
|
}
|
|
16726
|
-
app.db.delete(schedules).where(
|
|
16883
|
+
app.db.delete(schedules).where(eq18(schedules.id, schedule.id)).run();
|
|
16727
16884
|
writeAuditLog(app.db, {
|
|
16728
16885
|
projectId: project.id,
|
|
16729
16886
|
actor: "api",
|
|
@@ -16755,8 +16912,8 @@ function formatSchedule(row) {
|
|
|
16755
16912
|
}
|
|
16756
16913
|
|
|
16757
16914
|
// ../api-routes/src/notifications.ts
|
|
16758
|
-
import
|
|
16759
|
-
import { eq as
|
|
16915
|
+
import crypto14 from "crypto";
|
|
16916
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
16760
16917
|
var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
|
|
16761
16918
|
async function notificationRoutes(app, opts = {}) {
|
|
16762
16919
|
const allowLoopback = opts.allowLoopbackWebhooks === true;
|
|
@@ -16775,8 +16932,8 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
16775
16932
|
throw validationError(`Invalid event(s): ${invalid.join(", ")}. Must be one of: ${VALID_EVENTS.join(", ")}`);
|
|
16776
16933
|
}
|
|
16777
16934
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16778
|
-
const id =
|
|
16779
|
-
const webhookSecret =
|
|
16935
|
+
const id = crypto14.randomUUID();
|
|
16936
|
+
const webhookSecret = crypto14.randomBytes(32).toString("hex");
|
|
16780
16937
|
app.db.insert(notifications).values({
|
|
16781
16938
|
id,
|
|
16782
16939
|
projectId: project.id,
|
|
@@ -16796,22 +16953,22 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
16796
16953
|
diff: { channel, ...redactNotificationUrl(url), events }
|
|
16797
16954
|
});
|
|
16798
16955
|
return reply.status(201).send({
|
|
16799
|
-
...formatNotification(app.db.select().from(notifications).where(
|
|
16956
|
+
...formatNotification(app.db.select().from(notifications).where(eq19(notifications.id, id)).get()),
|
|
16800
16957
|
webhookSecret
|
|
16801
16958
|
});
|
|
16802
16959
|
});
|
|
16803
16960
|
app.get("/projects/:name/notifications", async (request, reply) => {
|
|
16804
16961
|
const project = resolveProject(app.db, request.params.name);
|
|
16805
|
-
const rows = app.db.select().from(notifications).where(
|
|
16962
|
+
const rows = app.db.select().from(notifications).where(eq19(notifications.projectId, project.id)).all();
|
|
16806
16963
|
return reply.send(rows.map(formatNotification));
|
|
16807
16964
|
});
|
|
16808
16965
|
app.delete("/projects/:name/notifications/:id", async (request, reply) => {
|
|
16809
16966
|
const project = resolveProject(app.db, request.params.name);
|
|
16810
|
-
const notification = app.db.select().from(notifications).where(
|
|
16967
|
+
const notification = app.db.select().from(notifications).where(eq19(notifications.id, request.params.id)).get();
|
|
16811
16968
|
if (!notification || notification.projectId !== project.id) {
|
|
16812
16969
|
throw notFound("Notification", request.params.id);
|
|
16813
16970
|
}
|
|
16814
|
-
app.db.delete(notifications).where(
|
|
16971
|
+
app.db.delete(notifications).where(eq19(notifications.id, notification.id)).run();
|
|
16815
16972
|
writeAuditLog(app.db, {
|
|
16816
16973
|
projectId: project.id,
|
|
16817
16974
|
actor: "api",
|
|
@@ -16823,7 +16980,7 @@ async function notificationRoutes(app, opts = {}) {
|
|
|
16823
16980
|
});
|
|
16824
16981
|
app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
|
|
16825
16982
|
const project = resolveProject(app.db, request.params.name);
|
|
16826
|
-
const notification = app.db.select().from(notifications).where(
|
|
16983
|
+
const notification = app.db.select().from(notifications).where(eq19(notifications.id, request.params.id)).get();
|
|
16827
16984
|
if (!notification || notification.projectId !== project.id) {
|
|
16828
16985
|
throw notFound("Notification", request.params.id);
|
|
16829
16986
|
}
|
|
@@ -16875,8 +17032,8 @@ function formatNotification(row) {
|
|
|
16875
17032
|
}
|
|
16876
17033
|
|
|
16877
17034
|
// ../api-routes/src/google.ts
|
|
16878
|
-
import
|
|
16879
|
-
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";
|
|
16880
17037
|
|
|
16881
17038
|
// ../api-routes/src/gbp-summary.ts
|
|
16882
17039
|
function computeMetricTotals(rows) {
|
|
@@ -17378,7 +17535,7 @@ async function inspectUrl(accessToken, inspectionUrl, siteUrl) {
|
|
|
17378
17535
|
}
|
|
17379
17536
|
|
|
17380
17537
|
// ../integration-google-analytics/src/ga4-client.ts
|
|
17381
|
-
import
|
|
17538
|
+
import crypto15 from "crypto";
|
|
17382
17539
|
|
|
17383
17540
|
// ../integration-google-analytics/src/constants.ts
|
|
17384
17541
|
var GA4_DATA_API_BASE = "https://analyticsdata.googleapis.com/v1beta";
|
|
@@ -17485,7 +17642,7 @@ function createServiceAccountJwt(clientEmail, privateKey, scope) {
|
|
|
17485
17642
|
const headerB64 = encode(header);
|
|
17486
17643
|
const payloadB64 = encode(payload);
|
|
17487
17644
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
17488
|
-
const sign =
|
|
17645
|
+
const sign = crypto15.createSign("RSA-SHA256");
|
|
17489
17646
|
sign.update(signingInput);
|
|
17490
17647
|
const signature = sign.sign(privateKey, "base64url");
|
|
17491
17648
|
return `${signingInput}.${signature}`;
|
|
@@ -18325,7 +18482,7 @@ async function listPlaceActionLinks(accessToken, locationName, opts = {}) {
|
|
|
18325
18482
|
}
|
|
18326
18483
|
|
|
18327
18484
|
// ../integration-google-business-profile/src/lodging-client.ts
|
|
18328
|
-
import
|
|
18485
|
+
import crypto16 from "crypto";
|
|
18329
18486
|
async function getLodging(accessToken, locationName, opts = {}) {
|
|
18330
18487
|
const url = `${GBP_LODGING_BASE}/${locationName}/lodging?readMask=*`;
|
|
18331
18488
|
try {
|
|
@@ -18352,7 +18509,7 @@ function isPopulated(value) {
|
|
|
18352
18509
|
return true;
|
|
18353
18510
|
}
|
|
18354
18511
|
function hashLodging(lodging) {
|
|
18355
|
-
return
|
|
18512
|
+
return crypto16.createHash("sha256").update(stableStringify2(lodging)).digest("hex");
|
|
18356
18513
|
}
|
|
18357
18514
|
function stableStringify2(value) {
|
|
18358
18515
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
@@ -18374,7 +18531,7 @@ function scopesForConnectionType(type) {
|
|
|
18374
18531
|
}
|
|
18375
18532
|
}
|
|
18376
18533
|
function signState(payload, secret) {
|
|
18377
|
-
return
|
|
18534
|
+
return crypto17.createHmac("sha256", secret).update(payload).digest("hex");
|
|
18378
18535
|
}
|
|
18379
18536
|
function buildSignedState(data, secret) {
|
|
18380
18537
|
const payload = JSON.stringify(data);
|
|
@@ -18385,7 +18542,7 @@ function verifySignedState(encoded, secret) {
|
|
|
18385
18542
|
try {
|
|
18386
18543
|
const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
|
|
18387
18544
|
const expected = signState(payload, secret);
|
|
18388
|
-
if (!
|
|
18545
|
+
if (!crypto17.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
|
|
18389
18546
|
return JSON.parse(payload);
|
|
18390
18547
|
} catch {
|
|
18391
18548
|
return null;
|
|
@@ -18540,7 +18697,7 @@ async function googleRoutes(app, opts) {
|
|
|
18540
18697
|
if (!projectId) {
|
|
18541
18698
|
return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
|
|
18542
18699
|
}
|
|
18543
|
-
const project = app.db.select().from(projects).where(
|
|
18700
|
+
const project = app.db.select().from(projects).where(eq20(projects.id, projectId)).get();
|
|
18544
18701
|
if (!project) {
|
|
18545
18702
|
return reply.status(400).send("Project no longer exists. Restart the connect flow.");
|
|
18546
18703
|
}
|
|
@@ -18655,7 +18812,7 @@ async function googleRoutes(app, opts) {
|
|
|
18655
18812
|
throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
|
|
18656
18813
|
}
|
|
18657
18814
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18658
|
-
const runId =
|
|
18815
|
+
const runId = crypto17.randomUUID();
|
|
18659
18816
|
app.db.insert(runs).values({
|
|
18660
18817
|
id: runId,
|
|
18661
18818
|
projectId: project.id,
|
|
@@ -18668,14 +18825,14 @@ async function googleRoutes(app, opts) {
|
|
|
18668
18825
|
if (opts.onGscSyncRequested) {
|
|
18669
18826
|
opts.onGscSyncRequested(runId, project.id, { days, full });
|
|
18670
18827
|
}
|
|
18671
|
-
const run = app.db.select().from(runs).where(
|
|
18828
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
18672
18829
|
return run;
|
|
18673
18830
|
});
|
|
18674
18831
|
app.get("/projects/:name/google/gsc/performance", async (request) => {
|
|
18675
18832
|
const project = resolveProject(app.db, request.params.name);
|
|
18676
18833
|
const { startDate, endDate, query, page, limit, offset } = request.query;
|
|
18677
18834
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
18678
|
-
const conditions = [
|
|
18835
|
+
const conditions = [eq20(gscSearchData.projectId, project.id)];
|
|
18679
18836
|
if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
|
|
18680
18837
|
else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
|
|
18681
18838
|
if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -18683,7 +18840,7 @@ async function googleRoutes(app, opts) {
|
|
|
18683
18840
|
if (page) conditions.push(sql8`${gscSearchData.page} LIKE ${"%" + page + "%"}`);
|
|
18684
18841
|
const limitVal = Math.max(parseInt(limit ?? "500", 10) || 0, 1);
|
|
18685
18842
|
const offsetVal = Math.max(parseInt(offset ?? "0", 10) || 0, 0);
|
|
18686
|
-
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();
|
|
18687
18844
|
return rows.map((r) => ({
|
|
18688
18845
|
date: r.date,
|
|
18689
18846
|
query: r.query,
|
|
@@ -18700,7 +18857,7 @@ async function googleRoutes(app, opts) {
|
|
|
18700
18857
|
const project = resolveProject(app.db, request.params.name);
|
|
18701
18858
|
const { startDate, endDate } = request.query;
|
|
18702
18859
|
const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
|
|
18703
|
-
const conditions = [
|
|
18860
|
+
const conditions = [eq20(gscSearchData.projectId, project.id)];
|
|
18704
18861
|
if (startDate) conditions.push(sql8`${gscSearchData.date} >= ${startDate}`);
|
|
18705
18862
|
else if (cutoffDate) conditions.push(sql8`${gscSearchData.date} >= ${cutoffDate}`);
|
|
18706
18863
|
if (endDate) conditions.push(sql8`${gscSearchData.date} <= ${endDate}`);
|
|
@@ -18748,7 +18905,7 @@ async function googleRoutes(app, opts) {
|
|
|
18748
18905
|
const mob = ir.mobileUsabilityResult;
|
|
18749
18906
|
const rich = ir.richResultsResult;
|
|
18750
18907
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18751
|
-
const id =
|
|
18908
|
+
const id = crypto17.randomUUID();
|
|
18752
18909
|
app.db.insert(gscUrlInspections).values({
|
|
18753
18910
|
id,
|
|
18754
18911
|
projectId: project.id,
|
|
@@ -18786,9 +18943,9 @@ async function googleRoutes(app, opts) {
|
|
|
18786
18943
|
app.get("/projects/:name/google/gsc/inspections", async (request) => {
|
|
18787
18944
|
const project = resolveProject(app.db, request.params.name);
|
|
18788
18945
|
const { url, limit } = request.query;
|
|
18789
|
-
const conditions = [
|
|
18790
|
-
if (url) conditions.push(
|
|
18791
|
-
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();
|
|
18792
18949
|
return rows.map((r) => ({
|
|
18793
18950
|
id: r.id,
|
|
18794
18951
|
url: r.url,
|
|
@@ -18807,7 +18964,7 @@ async function googleRoutes(app, opts) {
|
|
|
18807
18964
|
});
|
|
18808
18965
|
app.get("/projects/:name/google/gsc/deindexed", async (request) => {
|
|
18809
18966
|
const project = resolveProject(app.db, request.params.name);
|
|
18810
|
-
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();
|
|
18811
18968
|
const byUrl = /* @__PURE__ */ new Map();
|
|
18812
18969
|
for (const row of allInspections) {
|
|
18813
18970
|
const existing = byUrl.get(row.url);
|
|
@@ -18835,7 +18992,7 @@ async function googleRoutes(app, opts) {
|
|
|
18835
18992
|
});
|
|
18836
18993
|
app.get("/projects/:name/google/gsc/coverage", async (request) => {
|
|
18837
18994
|
const project = resolveProject(app.db, request.params.name);
|
|
18838
|
-
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();
|
|
18839
18996
|
const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
|
|
18840
18997
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
18841
18998
|
const historyByUrl = /* @__PURE__ */ new Map();
|
|
@@ -18884,7 +19041,7 @@ async function googleRoutes(app, opts) {
|
|
|
18884
19041
|
const total = latestByUrl.size;
|
|
18885
19042
|
const indexed = indexedUrls.length;
|
|
18886
19043
|
const notIndexed = notIndexedUrls.length;
|
|
18887
|
-
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();
|
|
18888
19045
|
const lastSyncedAt = latestSnapshot?.createdAt ?? null;
|
|
18889
19046
|
const formatRow = (r) => ({
|
|
18890
19047
|
id: r.id,
|
|
@@ -18935,7 +19092,7 @@ async function googleRoutes(app, opts) {
|
|
|
18935
19092
|
const project = resolveProject(app.db, request.params.name);
|
|
18936
19093
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
18937
19094
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
18938
|
-
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();
|
|
18939
19096
|
return rows.map((r) => ({
|
|
18940
19097
|
date: r.date,
|
|
18941
19098
|
indexed: r.indexed,
|
|
@@ -18983,7 +19140,7 @@ async function googleRoutes(app, opts) {
|
|
|
18983
19140
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18984
19141
|
});
|
|
18985
19142
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18986
|
-
const runId =
|
|
19143
|
+
const runId = crypto17.randomUUID();
|
|
18987
19144
|
app.db.insert(runs).values({
|
|
18988
19145
|
id: runId,
|
|
18989
19146
|
projectId: project.id,
|
|
@@ -18995,7 +19152,7 @@ async function googleRoutes(app, opts) {
|
|
|
18995
19152
|
if (opts.onInspectSitemapRequested) {
|
|
18996
19153
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
|
|
18997
19154
|
}
|
|
18998
|
-
const run = app.db.select().from(runs).where(
|
|
19155
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
18999
19156
|
return { sitemaps, primarySitemapUrl: sitemapUrl, run };
|
|
19000
19157
|
});
|
|
19001
19158
|
app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
|
|
@@ -19009,7 +19166,7 @@ async function googleRoutes(app, opts) {
|
|
|
19009
19166
|
throw validationError("No GSC property configured for this connection");
|
|
19010
19167
|
}
|
|
19011
19168
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19012
|
-
const runId =
|
|
19169
|
+
const runId = crypto17.randomUUID();
|
|
19013
19170
|
app.db.insert(runs).values({
|
|
19014
19171
|
id: runId,
|
|
19015
19172
|
projectId: project.id,
|
|
@@ -19022,7 +19179,7 @@ async function googleRoutes(app, opts) {
|
|
|
19022
19179
|
if (opts.onInspectSitemapRequested) {
|
|
19023
19180
|
opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
|
|
19024
19181
|
}
|
|
19025
|
-
const run = app.db.select().from(runs).where(
|
|
19182
|
+
const run = app.db.select().from(runs).where(eq20(runs.id, runId)).get();
|
|
19026
19183
|
return run;
|
|
19027
19184
|
});
|
|
19028
19185
|
app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
|
|
@@ -19069,7 +19226,7 @@ async function googleRoutes(app, opts) {
|
|
|
19069
19226
|
const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
|
|
19070
19227
|
let urlsToNotify = request.body?.urls ?? [];
|
|
19071
19228
|
if (request.body?.allUnindexed) {
|
|
19072
|
-
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();
|
|
19073
19230
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
19074
19231
|
for (const row of allInspections) {
|
|
19075
19232
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -19180,7 +19337,7 @@ async function googleRoutes(app, opts) {
|
|
|
19180
19337
|
};
|
|
19181
19338
|
}
|
|
19182
19339
|
function listSelectionResponse(projectId) {
|
|
19183
|
-
const rows = app.db.select().from(gbpLocations).where(
|
|
19340
|
+
const rows = app.db.select().from(gbpLocations).where(eq20(gbpLocations.projectId, projectId)).all();
|
|
19184
19341
|
const dtos = rows.map(rowToDto2);
|
|
19185
19342
|
return {
|
|
19186
19343
|
locations: dtos,
|
|
@@ -19189,15 +19346,15 @@ async function googleRoutes(app, opts) {
|
|
|
19189
19346
|
};
|
|
19190
19347
|
}
|
|
19191
19348
|
function clearGbpProjectData(tx, projectId) {
|
|
19192
|
-
tx.delete(gbpDailyMetrics).where(
|
|
19193
|
-
tx.delete(gbpKeywordImpressions).where(
|
|
19194
|
-
tx.delete(gbpKeywordMonthly).where(
|
|
19195
|
-
tx.delete(gbpPlaceActions).where(
|
|
19196
|
-
tx.delete(gbpLodgingSnapshots).where(
|
|
19197
|
-
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();
|
|
19198
19355
|
}
|
|
19199
19356
|
function currentProjectAccount(projectId) {
|
|
19200
|
-
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();
|
|
19201
19358
|
return row?.accountName ?? null;
|
|
19202
19359
|
}
|
|
19203
19360
|
app.post("/projects/:name/gbp/locations/discover", async (request) => {
|
|
@@ -19267,7 +19424,7 @@ async function googleRoutes(app, opts) {
|
|
|
19267
19424
|
app.db.transaction((tx) => {
|
|
19268
19425
|
if (switching) clearGbpProjectData(tx, project.id);
|
|
19269
19426
|
for (const remote of remoteLocations) {
|
|
19270
|
-
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();
|
|
19271
19428
|
if (existing) {
|
|
19272
19429
|
tx.update(gbpLocations).set({
|
|
19273
19430
|
accountName,
|
|
@@ -19278,10 +19435,10 @@ async function googleRoutes(app, opts) {
|
|
|
19278
19435
|
placeId: remote.metadata?.placeId ?? null,
|
|
19279
19436
|
mapsUri: remote.metadata?.mapsUri ?? null,
|
|
19280
19437
|
updatedAt: now
|
|
19281
|
-
}).where(
|
|
19438
|
+
}).where(eq20(gbpLocations.id, existing.id)).run();
|
|
19282
19439
|
} else {
|
|
19283
19440
|
tx.insert(gbpLocations).values({
|
|
19284
|
-
id:
|
|
19441
|
+
id: crypto17.randomUUID(),
|
|
19285
19442
|
projectId: project.id,
|
|
19286
19443
|
accountName,
|
|
19287
19444
|
locationName: remote.name,
|
|
@@ -19361,11 +19518,11 @@ async function googleRoutes(app, opts) {
|
|
|
19361
19518
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
|
|
19362
19519
|
}
|
|
19363
19520
|
const { selected } = parsed.data;
|
|
19364
|
-
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();
|
|
19365
19522
|
if (!existing) throw notFound("GBP location", locationName);
|
|
19366
19523
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19367
19524
|
app.db.transaction((tx) => {
|
|
19368
|
-
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(
|
|
19525
|
+
tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq20(gbpLocations.id, existing.id)).run();
|
|
19369
19526
|
writeAuditLog(tx, {
|
|
19370
19527
|
projectId: project.id,
|
|
19371
19528
|
actor: "api",
|
|
@@ -19374,7 +19531,7 @@ async function googleRoutes(app, opts) {
|
|
|
19374
19531
|
entityId: locationName
|
|
19375
19532
|
});
|
|
19376
19533
|
});
|
|
19377
|
-
const refreshed = app.db.select().from(gbpLocations).where(
|
|
19534
|
+
const refreshed = app.db.select().from(gbpLocations).where(eq20(gbpLocations.id, existing.id)).get();
|
|
19378
19535
|
return rowToDto2(refreshed);
|
|
19379
19536
|
});
|
|
19380
19537
|
app.delete("/projects/:name/gbp/connection", async (request, reply) => {
|
|
@@ -19404,7 +19561,7 @@ async function googleRoutes(app, opts) {
|
|
|
19404
19561
|
throw validationError(parsed.error.issues[0]?.message ?? "Invalid sync request");
|
|
19405
19562
|
}
|
|
19406
19563
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19407
|
-
const runId =
|
|
19564
|
+
const runId = crypto17.randomUUID();
|
|
19408
19565
|
app.db.insert(runs).values({
|
|
19409
19566
|
id: runId,
|
|
19410
19567
|
projectId: project.id,
|
|
@@ -19418,10 +19575,10 @@ async function googleRoutes(app, opts) {
|
|
|
19418
19575
|
});
|
|
19419
19576
|
app.get("/projects/:name/gbp/metrics", async (request) => {
|
|
19420
19577
|
const project = resolveProject(app.db, request.params.name);
|
|
19421
|
-
const conditions = [
|
|
19422
|
-
if (request.query.locationName) conditions.push(
|
|
19423
|
-
if (request.query.metric) conditions.push(
|
|
19424
|
-
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();
|
|
19425
19582
|
return {
|
|
19426
19583
|
metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
|
|
19427
19584
|
total: rows.length
|
|
@@ -19429,8 +19586,8 @@ async function googleRoutes(app, opts) {
|
|
|
19429
19586
|
});
|
|
19430
19587
|
app.get("/projects/:name/gbp/keywords", async (request) => {
|
|
19431
19588
|
const project = resolveProject(app.db, request.params.name);
|
|
19432
|
-
const conditions = [
|
|
19433
|
-
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));
|
|
19434
19591
|
const rows = app.db.select().from(gbpKeywordImpressions).where(and13(...conditions)).all();
|
|
19435
19592
|
rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
|
|
19436
19593
|
const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
|
|
@@ -19449,8 +19606,8 @@ async function googleRoutes(app, opts) {
|
|
|
19449
19606
|
});
|
|
19450
19607
|
app.get("/projects/:name/gbp/place-actions", async (request) => {
|
|
19451
19608
|
const project = resolveProject(app.db, request.params.name);
|
|
19452
|
-
const conditions = [
|
|
19453
|
-
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));
|
|
19454
19611
|
const rows = app.db.select().from(gbpPlaceActions).where(and13(...conditions)).all();
|
|
19455
19612
|
return {
|
|
19456
19613
|
placeActions: rows.map((r) => ({
|
|
@@ -19466,9 +19623,9 @@ async function googleRoutes(app, opts) {
|
|
|
19466
19623
|
});
|
|
19467
19624
|
app.get("/projects/:name/gbp/lodging", async (request) => {
|
|
19468
19625
|
const project = resolveProject(app.db, request.params.name);
|
|
19469
|
-
const conditions = [
|
|
19470
|
-
if (request.query.locationName) conditions.push(
|
|
19471
|
-
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();
|
|
19472
19629
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
19473
19630
|
for (const row of rows) {
|
|
19474
19631
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -19483,9 +19640,9 @@ async function googleRoutes(app, opts) {
|
|
|
19483
19640
|
});
|
|
19484
19641
|
app.get("/projects/:name/gbp/places", async (request) => {
|
|
19485
19642
|
const project = resolveProject(app.db, request.params.name);
|
|
19486
|
-
const conditions = [
|
|
19487
|
-
if (request.query.locationName) conditions.push(
|
|
19488
|
-
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();
|
|
19489
19646
|
const latestByLocation = /* @__PURE__ */ new Map();
|
|
19490
19647
|
for (const row of rows) {
|
|
19491
19648
|
if (!latestByLocation.has(row.locationName)) latestByLocation.set(row.locationName, row);
|
|
@@ -19504,7 +19661,7 @@ async function googleRoutes(app, opts) {
|
|
|
19504
19661
|
app.get("/projects/:name/gbp/summary", async (request) => {
|
|
19505
19662
|
const project = resolveProject(app.db, request.params.name);
|
|
19506
19663
|
const locationName = request.query.locationName ?? null;
|
|
19507
|
-
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);
|
|
19508
19665
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
19509
19666
|
if (locationNames.length === 0) {
|
|
19510
19667
|
return buildGbpSummary({
|
|
@@ -19517,10 +19674,10 @@ async function googleRoutes(app, opts) {
|
|
|
19517
19674
|
lodging: []
|
|
19518
19675
|
});
|
|
19519
19676
|
}
|
|
19520
|
-
const metricRows = app.db.select().from(gbpDailyMetrics).where(and13(
|
|
19521
|
-
const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and13(
|
|
19522
|
-
const placeActionRows = app.db.select().from(gbpPlaceActions).where(and13(
|
|
19523
|
-
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();
|
|
19524
19681
|
const latestLodgingByLocation = /* @__PURE__ */ new Map();
|
|
19525
19682
|
for (const row of lodgingRows) {
|
|
19526
19683
|
if (!latestLodgingByLocation.has(row.locationName)) {
|
|
@@ -19540,8 +19697,8 @@ async function googleRoutes(app, opts) {
|
|
|
19540
19697
|
}
|
|
19541
19698
|
|
|
19542
19699
|
// ../api-routes/src/bing.ts
|
|
19543
|
-
import
|
|
19544
|
-
import { eq as
|
|
19700
|
+
import crypto18 from "crypto";
|
|
19701
|
+
import { eq as eq21, and as and14, desc as desc11 } from "drizzle-orm";
|
|
19545
19702
|
|
|
19546
19703
|
// ../integration-bing/src/constants.ts
|
|
19547
19704
|
var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
|
|
@@ -19869,7 +20026,7 @@ async function bingRoutes(app, opts) {
|
|
|
19869
20026
|
const store = requireConnectionStore();
|
|
19870
20027
|
const project = resolveProject(app.db, request.params.name);
|
|
19871
20028
|
requireConnection(store, project.canonicalDomain);
|
|
19872
|
-
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();
|
|
19873
20030
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
19874
20031
|
const definitiveByUrl = /* @__PURE__ */ new Map();
|
|
19875
20032
|
for (const row of allInspections) {
|
|
@@ -19926,7 +20083,7 @@ async function bingRoutes(app, opts) {
|
|
|
19926
20083
|
const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
19927
20084
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19928
20085
|
app.db.insert(bingCoverageSnapshots).values({
|
|
19929
|
-
id:
|
|
20086
|
+
id: crypto18.randomUUID(),
|
|
19930
20087
|
projectId: project.id,
|
|
19931
20088
|
syncRunId: snapshotRunId,
|
|
19932
20089
|
date: snapshotDate,
|
|
@@ -19958,7 +20115,7 @@ async function bingRoutes(app, opts) {
|
|
|
19958
20115
|
const project = resolveProject(app.db, request.params.name);
|
|
19959
20116
|
const parsed = parseInt(request.query.limit ?? "90", 10);
|
|
19960
20117
|
const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
|
|
19961
|
-
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();
|
|
19962
20119
|
return rows.map((r) => ({
|
|
19963
20120
|
date: r.date,
|
|
19964
20121
|
indexed: r.indexed,
|
|
@@ -19970,8 +20127,8 @@ async function bingRoutes(app, opts) {
|
|
|
19970
20127
|
requireConnectionStore();
|
|
19971
20128
|
const project = resolveProject(app.db, request.params.name);
|
|
19972
20129
|
const { url, limit } = request.query;
|
|
19973
|
-
const whereClause = url ? and14(
|
|
19974
|
-
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();
|
|
19975
20132
|
return filtered.map((r) => ({
|
|
19976
20133
|
id: r.id,
|
|
19977
20134
|
url: r.url,
|
|
@@ -19997,7 +20154,7 @@ async function bingRoutes(app, opts) {
|
|
|
19997
20154
|
throw validationError("url is required");
|
|
19998
20155
|
}
|
|
19999
20156
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20000
|
-
const runId =
|
|
20157
|
+
const runId = crypto18.randomUUID();
|
|
20001
20158
|
app.db.insert(runs).values({
|
|
20002
20159
|
id: runId,
|
|
20003
20160
|
projectId: project.id,
|
|
@@ -20018,7 +20175,7 @@ async function bingRoutes(app, opts) {
|
|
|
20018
20175
|
discoveryDate: result.DiscoveryDate ?? null
|
|
20019
20176
|
});
|
|
20020
20177
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20021
|
-
const id =
|
|
20178
|
+
const id = crypto18.randomUUID();
|
|
20022
20179
|
const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
|
|
20023
20180
|
const lastCrawledDate = parseBingDate(result.LastCrawledDate);
|
|
20024
20181
|
const inIndexDate = parseBingDate(result.InIndexDate);
|
|
@@ -20060,7 +20217,7 @@ async function bingRoutes(app, opts) {
|
|
|
20060
20217
|
anchorCount: result.AnchorCount ?? null,
|
|
20061
20218
|
discoveryDate
|
|
20062
20219
|
}).run();
|
|
20063
|
-
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();
|
|
20064
20221
|
return {
|
|
20065
20222
|
id,
|
|
20066
20223
|
url,
|
|
@@ -20076,7 +20233,7 @@ async function bingRoutes(app, opts) {
|
|
|
20076
20233
|
} catch (e) {
|
|
20077
20234
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20078
20235
|
bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
|
|
20079
|
-
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();
|
|
20080
20237
|
throw e;
|
|
20081
20238
|
}
|
|
20082
20239
|
});
|
|
@@ -20088,7 +20245,7 @@ async function bingRoutes(app, opts) {
|
|
|
20088
20245
|
throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
20089
20246
|
}
|
|
20090
20247
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20091
|
-
const runId =
|
|
20248
|
+
const runId = crypto18.randomUUID();
|
|
20092
20249
|
app.db.insert(runs).values({
|
|
20093
20250
|
id: runId,
|
|
20094
20251
|
projectId: project.id,
|
|
@@ -20103,7 +20260,7 @@ async function bingRoutes(app, opts) {
|
|
|
20103
20260
|
} else {
|
|
20104
20261
|
bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
|
|
20105
20262
|
}
|
|
20106
|
-
const run = app.db.select().from(runs).where(
|
|
20263
|
+
const run = app.db.select().from(runs).where(eq21(runs.id, runId)).get();
|
|
20107
20264
|
return run;
|
|
20108
20265
|
});
|
|
20109
20266
|
app.post("/projects/:name/bing/request-indexing", async (request) => {
|
|
@@ -20115,7 +20272,7 @@ async function bingRoutes(app, opts) {
|
|
|
20115
20272
|
}
|
|
20116
20273
|
let urlsToSubmit = request.body?.urls ?? [];
|
|
20117
20274
|
if (request.body?.allUnindexed) {
|
|
20118
|
-
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();
|
|
20119
20276
|
const latestByUrl = /* @__PURE__ */ new Map();
|
|
20120
20277
|
for (const row of allInspections) {
|
|
20121
20278
|
if (!latestByUrl.has(row.url)) {
|
|
@@ -20202,14 +20359,14 @@ async function bingRoutes(app, opts) {
|
|
|
20202
20359
|
import fs from "fs";
|
|
20203
20360
|
import path from "path";
|
|
20204
20361
|
import os from "os";
|
|
20205
|
-
import { eq as
|
|
20362
|
+
import { eq as eq22, and as and15 } from "drizzle-orm";
|
|
20206
20363
|
function getScreenshotDir() {
|
|
20207
20364
|
return path.join(os.homedir(), ".canonry", "screenshots");
|
|
20208
20365
|
}
|
|
20209
20366
|
async function cdpRoutes(app, opts) {
|
|
20210
20367
|
app.get("/screenshots/:snapshotId", async (request, reply) => {
|
|
20211
20368
|
const { snapshotId } = request.params;
|
|
20212
|
-
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();
|
|
20213
20370
|
if (!snapshot?.screenshotPath) {
|
|
20214
20371
|
const err = notFound("Screenshot", snapshotId);
|
|
20215
20372
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -20276,7 +20433,7 @@ async function cdpRoutes(app, opts) {
|
|
|
20276
20433
|
async (request, reply) => {
|
|
20277
20434
|
const project = resolveProject(app.db, request.params.name);
|
|
20278
20435
|
const { runId } = request.params;
|
|
20279
|
-
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();
|
|
20280
20437
|
if (!run) {
|
|
20281
20438
|
const err = notFound("Run", runId);
|
|
20282
20439
|
return reply.code(err.statusCode).send(err.toJSON());
|
|
@@ -20289,8 +20446,8 @@ async function cdpRoutes(app, opts) {
|
|
|
20289
20446
|
citedDomains: querySnapshots.citedDomains,
|
|
20290
20447
|
screenshotPath: querySnapshots.screenshotPath,
|
|
20291
20448
|
rawResponse: querySnapshots.rawResponse
|
|
20292
|
-
}).from(querySnapshots).where(
|
|
20293
|
-
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();
|
|
20294
20451
|
const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
|
|
20295
20452
|
const byQuery = /* @__PURE__ */ new Map();
|
|
20296
20453
|
for (const snap of snapshots) {
|
|
@@ -20372,8 +20529,8 @@ async function cdpRoutes(app, opts) {
|
|
|
20372
20529
|
}
|
|
20373
20530
|
|
|
20374
20531
|
// ../api-routes/src/ga.ts
|
|
20375
|
-
import
|
|
20376
|
-
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";
|
|
20377
20534
|
function gaLog(level, action, ctx) {
|
|
20378
20535
|
const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
|
|
20379
20536
|
const stream = level === "error" ? process.stderr : process.stdout;
|
|
@@ -20580,10 +20737,10 @@ async function ga4Routes(app, opts) {
|
|
|
20580
20737
|
if (!saConn && !oauthConn) {
|
|
20581
20738
|
throw notFound("GA4 connection", project.name);
|
|
20582
20739
|
}
|
|
20583
|
-
app.db.delete(gaTrafficSnapshots).where(
|
|
20584
|
-
app.db.delete(gaTrafficSummaries).where(
|
|
20585
|
-
app.db.delete(gaAiReferrals).where(
|
|
20586
|
-
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();
|
|
20587
20744
|
const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
|
|
20588
20745
|
opts.ga4CredentialStore?.deleteConnection(project.name);
|
|
20589
20746
|
opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
|
|
@@ -20604,7 +20761,7 @@ async function ga4Routes(app, opts) {
|
|
|
20604
20761
|
if (!connected) {
|
|
20605
20762
|
return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
|
|
20606
20763
|
}
|
|
20607
|
-
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();
|
|
20608
20765
|
return {
|
|
20609
20766
|
connected: true,
|
|
20610
20767
|
propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
|
|
@@ -20628,7 +20785,7 @@ async function ga4Routes(app, opts) {
|
|
|
20628
20785
|
const syncAi = !only || only === "ai";
|
|
20629
20786
|
const syncSocial = !only || only === "social";
|
|
20630
20787
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
20631
|
-
const runId =
|
|
20788
|
+
const runId = crypto19.randomUUID();
|
|
20632
20789
|
app.db.insert(runs).values({
|
|
20633
20790
|
id: runId,
|
|
20634
20791
|
projectId: project.id,
|
|
@@ -20669,14 +20826,14 @@ async function ga4Routes(app, opts) {
|
|
|
20669
20826
|
if (syncTraffic) {
|
|
20670
20827
|
tx.delete(gaTrafficSnapshots).where(
|
|
20671
20828
|
and16(
|
|
20672
|
-
|
|
20829
|
+
eq23(gaTrafficSnapshots.projectId, project.id),
|
|
20673
20830
|
sql9`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
|
|
20674
20831
|
sql9`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
|
|
20675
20832
|
)
|
|
20676
20833
|
).run();
|
|
20677
20834
|
for (const row of rows) {
|
|
20678
20835
|
tx.insert(gaTrafficSnapshots).values({
|
|
20679
|
-
id:
|
|
20836
|
+
id: crypto19.randomUUID(),
|
|
20680
20837
|
projectId: project.id,
|
|
20681
20838
|
date: row.date,
|
|
20682
20839
|
landingPage: row.landingPage,
|
|
@@ -20693,14 +20850,14 @@ async function ga4Routes(app, opts) {
|
|
|
20693
20850
|
if (syncAi) {
|
|
20694
20851
|
tx.delete(gaAiReferrals).where(
|
|
20695
20852
|
and16(
|
|
20696
|
-
|
|
20853
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
20697
20854
|
sql9`${gaAiReferrals.date} >= ${summary.periodStart}`,
|
|
20698
20855
|
sql9`${gaAiReferrals.date} <= ${summary.periodEnd}`
|
|
20699
20856
|
)
|
|
20700
20857
|
).run();
|
|
20701
20858
|
for (const row of aiReferrals) {
|
|
20702
20859
|
tx.insert(gaAiReferrals).values({
|
|
20703
|
-
id:
|
|
20860
|
+
id: crypto19.randomUUID(),
|
|
20704
20861
|
projectId: project.id,
|
|
20705
20862
|
date: row.date,
|
|
20706
20863
|
source: row.source,
|
|
@@ -20719,14 +20876,14 @@ async function ga4Routes(app, opts) {
|
|
|
20719
20876
|
if (syncSocial) {
|
|
20720
20877
|
tx.delete(gaSocialReferrals).where(
|
|
20721
20878
|
and16(
|
|
20722
|
-
|
|
20879
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
20723
20880
|
sql9`${gaSocialReferrals.date} >= ${summary.periodStart}`,
|
|
20724
20881
|
sql9`${gaSocialReferrals.date} <= ${summary.periodEnd}`
|
|
20725
20882
|
)
|
|
20726
20883
|
).run();
|
|
20727
20884
|
for (const row of socialReferrals) {
|
|
20728
20885
|
tx.insert(gaSocialReferrals).values({
|
|
20729
|
-
id:
|
|
20886
|
+
id: crypto19.randomUUID(),
|
|
20730
20887
|
projectId: project.id,
|
|
20731
20888
|
date: row.date,
|
|
20732
20889
|
source: row.source,
|
|
@@ -20740,9 +20897,9 @@ async function ga4Routes(app, opts) {
|
|
|
20740
20897
|
}
|
|
20741
20898
|
}
|
|
20742
20899
|
if (syncSummary) {
|
|
20743
|
-
tx.delete(gaTrafficSummaries).where(
|
|
20900
|
+
tx.delete(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).run();
|
|
20744
20901
|
tx.insert(gaTrafficSummaries).values({
|
|
20745
|
-
id:
|
|
20902
|
+
id: crypto19.randomUUID(),
|
|
20746
20903
|
projectId: project.id,
|
|
20747
20904
|
periodStart: summary.periodStart,
|
|
20748
20905
|
periodEnd: summary.periodEnd,
|
|
@@ -20752,10 +20909,10 @@ async function ga4Routes(app, opts) {
|
|
|
20752
20909
|
syncedAt: now,
|
|
20753
20910
|
syncRunId: runId
|
|
20754
20911
|
}).run();
|
|
20755
|
-
tx.delete(gaTrafficWindowSummaries).where(
|
|
20912
|
+
tx.delete(gaTrafficWindowSummaries).where(eq23(gaTrafficWindowSummaries.projectId, project.id)).run();
|
|
20756
20913
|
for (const ws of windowSummaries) {
|
|
20757
20914
|
tx.insert(gaTrafficWindowSummaries).values({
|
|
20758
|
-
id:
|
|
20915
|
+
id: crypto19.randomUUID(),
|
|
20759
20916
|
projectId: project.id,
|
|
20760
20917
|
windowKey: ws.windowKey,
|
|
20761
20918
|
periodStart: ws.periodStart,
|
|
@@ -20770,7 +20927,7 @@ async function ga4Routes(app, opts) {
|
|
|
20770
20927
|
}
|
|
20771
20928
|
}
|
|
20772
20929
|
});
|
|
20773
|
-
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();
|
|
20774
20931
|
const syncedComponents = only ? [
|
|
20775
20932
|
...syncTraffic ? ["traffic"] : [],
|
|
20776
20933
|
...syncSummary ? ["summary"] : [],
|
|
@@ -20799,7 +20956,7 @@ async function ga4Routes(app, opts) {
|
|
|
20799
20956
|
} catch (e) {
|
|
20800
20957
|
const msg = e instanceof Error ? e.message : String(e);
|
|
20801
20958
|
gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
|
|
20802
|
-
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();
|
|
20803
20960
|
throw e;
|
|
20804
20961
|
}
|
|
20805
20962
|
});
|
|
@@ -20810,11 +20967,11 @@ async function ga4Routes(app, opts) {
|
|
|
20810
20967
|
const window = parseWindow(request.query.window);
|
|
20811
20968
|
const cutoff = windowCutoff(window);
|
|
20812
20969
|
const cutoffDate = cutoff?.slice(0, 10) ?? null;
|
|
20813
|
-
const snapshotConditions = [
|
|
20970
|
+
const snapshotConditions = [eq23(gaTrafficSnapshots.projectId, project.id)];
|
|
20814
20971
|
if (cutoffDate) snapshotConditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
20815
|
-
const aiConditions = [
|
|
20972
|
+
const aiConditions = [eq23(gaAiReferrals.projectId, project.id)];
|
|
20816
20973
|
if (cutoffDate) aiConditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
20817
|
-
const socialConditions = [
|
|
20974
|
+
const socialConditions = [eq23(gaSocialReferrals.projectId, project.id)];
|
|
20818
20975
|
if (cutoffDate) socialConditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
20819
20976
|
const windowSummaryRow = cutoffDate ? app.db.select({
|
|
20820
20977
|
totalSessions: gaTrafficWindowSummaries.totalSessions,
|
|
@@ -20823,8 +20980,8 @@ async function ga4Routes(app, opts) {
|
|
|
20823
20980
|
totalUsers: gaTrafficWindowSummaries.totalUsers
|
|
20824
20981
|
}).from(gaTrafficWindowSummaries).where(
|
|
20825
20982
|
and16(
|
|
20826
|
-
|
|
20827
|
-
|
|
20983
|
+
eq23(gaTrafficWindowSummaries.projectId, project.id),
|
|
20984
|
+
eq23(gaTrafficWindowSummaries.windowKey, window)
|
|
20828
20985
|
)
|
|
20829
20986
|
).get() : null;
|
|
20830
20987
|
const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
|
|
@@ -20836,14 +20993,14 @@ async function ga4Routes(app, opts) {
|
|
|
20836
20993
|
totalSessions: gaTrafficSummaries.totalSessions,
|
|
20837
20994
|
totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
|
|
20838
20995
|
totalUsers: gaTrafficSummaries.totalUsers
|
|
20839
|
-
}).from(gaTrafficSummaries).where(
|
|
20996
|
+
}).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).get();
|
|
20840
20997
|
const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
|
|
20841
20998
|
totalDirectSessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
|
|
20842
20999
|
}).from(gaTrafficSnapshots).where(and16(...snapshotConditions)).get();
|
|
20843
21000
|
const summaryMeta = app.db.select({
|
|
20844
21001
|
periodStart: gaTrafficSummaries.periodStart,
|
|
20845
21002
|
periodEnd: gaTrafficSummaries.periodEnd
|
|
20846
|
-
}).from(gaTrafficSummaries).where(
|
|
21003
|
+
}).from(gaTrafficSummaries).where(eq23(gaTrafficSummaries.projectId, project.id)).get();
|
|
20847
21004
|
const rows = app.db.select({
|
|
20848
21005
|
landingPage: sql9`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
|
|
20849
21006
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
@@ -20902,7 +21059,7 @@ async function ga4Routes(app, opts) {
|
|
|
20902
21059
|
channelGroup: gaAiReferrals.channelGroup,
|
|
20903
21060
|
sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)`,
|
|
20904
21061
|
users: sql9`COALESCE(SUM(${gaAiReferrals.users}), 0)`
|
|
20905
|
-
}).from(gaAiReferrals).where(and16(...aiConditions,
|
|
21062
|
+
}).from(gaAiReferrals).where(and16(...aiConditions, eq23(gaAiReferrals.sourceDimension, "session"))).groupBy(gaAiReferrals.channelGroup).all();
|
|
20906
21063
|
const aiSessionsByChannelGroup = /* @__PURE__ */ new Map();
|
|
20907
21064
|
let aiBySessionUsers = 0;
|
|
20908
21065
|
for (const row of aiBySessionRows) {
|
|
@@ -20921,7 +21078,7 @@ async function ga4Routes(app, opts) {
|
|
|
20921
21078
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`,
|
|
20922
21079
|
users: sql9`SUM(${gaSocialReferrals.users})`
|
|
20923
21080
|
}).from(gaSocialReferrals).where(and16(...socialConditions)).get();
|
|
20924
|
-
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();
|
|
20925
21082
|
const total = summaryRow?.totalSessions ?? 0;
|
|
20926
21083
|
const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
|
|
20927
21084
|
const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
|
|
@@ -21001,7 +21158,7 @@ async function ga4Routes(app, opts) {
|
|
|
21001
21158
|
const project = resolveProject(app.db, request.params.name);
|
|
21002
21159
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
21003
21160
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
21004
|
-
const conditions = [
|
|
21161
|
+
const conditions = [eq23(gaAiReferrals.projectId, project.id)];
|
|
21005
21162
|
if (cutoffDate) conditions.push(sql9`${gaAiReferrals.date} >= ${cutoffDate}`);
|
|
21006
21163
|
const rows = app.db.select({
|
|
21007
21164
|
date: gaAiReferrals.date,
|
|
@@ -21024,7 +21181,7 @@ async function ga4Routes(app, opts) {
|
|
|
21024
21181
|
const project = resolveProject(app.db, request.params.name);
|
|
21025
21182
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
21026
21183
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
21027
|
-
const conditions = [
|
|
21184
|
+
const conditions = [eq23(gaSocialReferrals.projectId, project.id)];
|
|
21028
21185
|
if (cutoffDate) conditions.push(sql9`${gaSocialReferrals.date} >= ${cutoffDate}`);
|
|
21029
21186
|
const rows = app.db.select({
|
|
21030
21187
|
date: gaSocialReferrals.date,
|
|
@@ -21047,7 +21204,7 @@ async function ga4Routes(app, opts) {
|
|
|
21047
21204
|
return fmt(d);
|
|
21048
21205
|
};
|
|
21049
21206
|
const sumSocial = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and16(
|
|
21050
|
-
|
|
21207
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
21051
21208
|
sql9`${gaSocialReferrals.date} >= ${from}`,
|
|
21052
21209
|
sql9`${gaSocialReferrals.date} < ${to}`
|
|
21053
21210
|
)).get();
|
|
@@ -21060,7 +21217,7 @@ async function ga4Routes(app, opts) {
|
|
|
21060
21217
|
source: gaSocialReferrals.source,
|
|
21061
21218
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`
|
|
21062
21219
|
}).from(gaSocialReferrals).where(and16(
|
|
21063
|
-
|
|
21220
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
21064
21221
|
sql9`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
|
|
21065
21222
|
sql9`${gaSocialReferrals.date} < ${fmt(today)}`
|
|
21066
21223
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -21068,7 +21225,7 @@ async function ga4Routes(app, opts) {
|
|
|
21068
21225
|
source: gaSocialReferrals.source,
|
|
21069
21226
|
sessions: sql9`SUM(${gaSocialReferrals.sessions})`
|
|
21070
21227
|
}).from(gaSocialReferrals).where(and16(
|
|
21071
|
-
|
|
21228
|
+
eq23(gaSocialReferrals.projectId, project.id),
|
|
21072
21229
|
sql9`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
|
|
21073
21230
|
sql9`${gaSocialReferrals.date} < ${daysAgo(7)}`
|
|
21074
21231
|
)).groupBy(gaSocialReferrals.source).all();
|
|
@@ -21109,16 +21266,16 @@ async function ga4Routes(app, opts) {
|
|
|
21109
21266
|
return fmt(d);
|
|
21110
21267
|
};
|
|
21111
21268
|
const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
|
|
21112
|
-
const sumTotal = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and16(
|
|
21113
|
-
const sumOrganic = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and16(
|
|
21114
|
-
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();
|
|
21115
21272
|
const sumAi = (from, to) => app.db.select({ sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
|
|
21116
|
-
|
|
21273
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
21117
21274
|
sql9`${gaAiReferrals.date} >= ${from}`,
|
|
21118
21275
|
sql9`${gaAiReferrals.date} < ${to}`,
|
|
21119
|
-
|
|
21276
|
+
eq23(gaAiReferrals.sourceDimension, "session")
|
|
21120
21277
|
)).get();
|
|
21121
|
-
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();
|
|
21122
21279
|
const todayStr = fmt(today);
|
|
21123
21280
|
const buildTrend = (sum) => {
|
|
21124
21281
|
const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
|
|
@@ -21128,16 +21285,16 @@ async function ga4Routes(app, opts) {
|
|
|
21128
21285
|
return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
|
|
21129
21286
|
};
|
|
21130
21287
|
const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
|
|
21131
|
-
|
|
21288
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
21132
21289
|
sql9`${gaAiReferrals.date} >= ${daysAgo(7)}`,
|
|
21133
21290
|
sql9`${gaAiReferrals.date} < ${todayStr}`,
|
|
21134
|
-
|
|
21291
|
+
eq23(gaAiReferrals.sourceDimension, "session")
|
|
21135
21292
|
)).groupBy(gaAiReferrals.source).all();
|
|
21136
21293
|
const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql9`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and16(
|
|
21137
|
-
|
|
21294
|
+
eq23(gaAiReferrals.projectId, project.id),
|
|
21138
21295
|
sql9`${gaAiReferrals.date} >= ${daysAgo(14)}`,
|
|
21139
21296
|
sql9`${gaAiReferrals.date} < ${daysAgo(7)}`,
|
|
21140
|
-
|
|
21297
|
+
eq23(gaAiReferrals.sourceDimension, "session")
|
|
21141
21298
|
)).groupBy(gaAiReferrals.source).all();
|
|
21142
21299
|
const findBiggestMover = (current, prev) => {
|
|
21143
21300
|
const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
|
|
@@ -21153,8 +21310,8 @@ async function ga4Routes(app, opts) {
|
|
|
21153
21310
|
}
|
|
21154
21311
|
return mover;
|
|
21155
21312
|
};
|
|
21156
|
-
const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql9`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and16(
|
|
21157
|
-
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();
|
|
21158
21315
|
return {
|
|
21159
21316
|
total: buildTrend(sumTotal),
|
|
21160
21317
|
organic: buildTrend(sumOrganic),
|
|
@@ -21169,7 +21326,7 @@ async function ga4Routes(app, opts) {
|
|
|
21169
21326
|
const project = resolveProject(app.db, request.params.name);
|
|
21170
21327
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
21171
21328
|
const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
|
|
21172
|
-
const conditions = [
|
|
21329
|
+
const conditions = [eq23(gaTrafficSnapshots.projectId, project.id)];
|
|
21173
21330
|
if (cutoffDate) conditions.push(sql9`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
|
|
21174
21331
|
const rows = app.db.select({
|
|
21175
21332
|
date: gaTrafficSnapshots.date,
|
|
@@ -21192,7 +21349,7 @@ async function ga4Routes(app, opts) {
|
|
|
21192
21349
|
sessions: sql9`SUM(${gaTrafficSnapshots.sessions})`,
|
|
21193
21350
|
organicSessions: sql9`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
21194
21351
|
users: sql9`SUM(${gaTrafficSnapshots.users})`
|
|
21195
|
-
}).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();
|
|
21196
21353
|
return {
|
|
21197
21354
|
pages: trafficPages.map((r) => ({
|
|
21198
21355
|
landingPage: r.landingPage,
|
|
@@ -21326,7 +21483,7 @@ function parseSchemaPageEntry(entry) {
|
|
|
21326
21483
|
}
|
|
21327
21484
|
|
|
21328
21485
|
// ../integration-wordpress/src/wordpress-client.ts
|
|
21329
|
-
import
|
|
21486
|
+
import crypto20 from "crypto";
|
|
21330
21487
|
function validateUsername(username) {
|
|
21331
21488
|
if (!username || typeof username !== "string" || username.trim().length === 0) {
|
|
21332
21489
|
throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
|
|
@@ -21539,7 +21696,7 @@ function buildSnippet(content) {
|
|
|
21539
21696
|
return `${text2.slice(0, 157)}...`;
|
|
21540
21697
|
}
|
|
21541
21698
|
function contentHash(content) {
|
|
21542
|
-
return
|
|
21699
|
+
return crypto20.createHash("sha256").update(content).digest("hex");
|
|
21543
21700
|
}
|
|
21544
21701
|
function buildAmbiguousSlugMessage(slug, pages) {
|
|
21545
21702
|
const candidates = pages.map((page) => {
|
|
@@ -22828,8 +22985,8 @@ async function wordpressRoutes(app, opts) {
|
|
|
22828
22985
|
}
|
|
22829
22986
|
|
|
22830
22987
|
// ../api-routes/src/backlinks.ts
|
|
22831
|
-
import
|
|
22832
|
-
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";
|
|
22833
22990
|
|
|
22834
22991
|
// ../integration-commoncrawl/src/constants.ts
|
|
22835
22992
|
import os2 from "os";
|
|
@@ -23316,8 +23473,8 @@ function mapRunRow(row) {
|
|
|
23316
23473
|
};
|
|
23317
23474
|
}
|
|
23318
23475
|
function latestSummaryForProject(db, projectId, release) {
|
|
23319
|
-
const condition = release ? and18(
|
|
23320
|
-
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();
|
|
23321
23478
|
}
|
|
23322
23479
|
function parseExcludeCrawlers(value) {
|
|
23323
23480
|
if (!value) return false;
|
|
@@ -23326,8 +23483,8 @@ function parseExcludeCrawlers(value) {
|
|
|
23326
23483
|
}
|
|
23327
23484
|
function computeFilteredSummary(db, base) {
|
|
23328
23485
|
const baseDomainCondition = and18(
|
|
23329
|
-
|
|
23330
|
-
|
|
23486
|
+
eq24(backlinkDomains.projectId, base.projectId),
|
|
23487
|
+
eq24(backlinkDomains.release, base.release)
|
|
23331
23488
|
);
|
|
23332
23489
|
const filteredCondition = and18(baseDomainCondition, backlinkCrawlerExclusionClause());
|
|
23333
23490
|
const unfilteredAgg = db.select({
|
|
@@ -23338,7 +23495,7 @@ function computeFilteredSummary(db, base) {
|
|
|
23338
23495
|
count: sql10`count(*)`,
|
|
23339
23496
|
total: sql10`coalesce(sum(${backlinkDomains.numHosts}), 0)`
|
|
23340
23497
|
}).from(backlinkDomains).where(filteredCondition).get();
|
|
23341
|
-
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();
|
|
23342
23499
|
const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
|
|
23343
23500
|
const totalHosts = Number(filteredAgg?.total ?? 0);
|
|
23344
23501
|
const unfilteredLinkingDomains = Number(unfilteredAgg?.count ?? 0);
|
|
@@ -23398,7 +23555,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23398
23555
|
"@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
|
|
23399
23556
|
);
|
|
23400
23557
|
}
|
|
23401
|
-
const existing = app.db.select().from(ccReleaseSyncs).where(
|
|
23558
|
+
const existing = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.release, release)).get();
|
|
23402
23559
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23403
23560
|
if (existing) {
|
|
23404
23561
|
if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
|
|
@@ -23409,12 +23566,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
23409
23566
|
phaseDetail: null,
|
|
23410
23567
|
error: null,
|
|
23411
23568
|
updatedAt: now
|
|
23412
|
-
}).where(
|
|
23569
|
+
}).where(eq24(ccReleaseSyncs.id, existing.id)).run();
|
|
23413
23570
|
opts.onReleaseSyncRequested(existing.id, release);
|
|
23414
|
-
const refreshed = app.db.select().from(ccReleaseSyncs).where(
|
|
23571
|
+
const refreshed = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.id, existing.id)).get();
|
|
23415
23572
|
return reply.status(200).send(mapSyncRow(refreshed));
|
|
23416
23573
|
}
|
|
23417
|
-
const id =
|
|
23574
|
+
const id = crypto21.randomUUID();
|
|
23418
23575
|
app.db.insert(ccReleaseSyncs).values({
|
|
23419
23576
|
id,
|
|
23420
23577
|
release,
|
|
@@ -23423,15 +23580,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
23423
23580
|
updatedAt: now
|
|
23424
23581
|
}).run();
|
|
23425
23582
|
opts.onReleaseSyncRequested(id, release);
|
|
23426
|
-
const inserted = app.db.select().from(ccReleaseSyncs).where(
|
|
23583
|
+
const inserted = app.db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.id, id)).get();
|
|
23427
23584
|
return reply.status(201).send(mapSyncRow(inserted));
|
|
23428
23585
|
});
|
|
23429
23586
|
app.get("/backlinks/syncs/latest", async (_request, reply) => {
|
|
23430
|
-
const row = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
23587
|
+
const row = app.db.select().from(ccReleaseSyncs).orderBy(desc13(ccReleaseSyncs.updatedAt)).limit(1).get();
|
|
23431
23588
|
return reply.send(row ? mapSyncRow(row) : null);
|
|
23432
23589
|
});
|
|
23433
23590
|
app.get("/backlinks/syncs", async (_request, reply) => {
|
|
23434
|
-
const rows = app.db.select().from(ccReleaseSyncs).orderBy(
|
|
23591
|
+
const rows = app.db.select().from(ccReleaseSyncs).orderBy(desc13(ccReleaseSyncs.updatedAt)).all();
|
|
23435
23592
|
return reply.send(rows.map(mapSyncRow));
|
|
23436
23593
|
});
|
|
23437
23594
|
app.get("/backlinks/releases", async (_request, reply) => {
|
|
@@ -23471,7 +23628,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23471
23628
|
throw validationError("Invalid release id");
|
|
23472
23629
|
}
|
|
23473
23630
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23474
|
-
const runId =
|
|
23631
|
+
const runId = crypto21.randomUUID();
|
|
23475
23632
|
app.db.insert(runs).values({
|
|
23476
23633
|
id: runId,
|
|
23477
23634
|
projectId: project.id,
|
|
@@ -23481,7 +23638,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23481
23638
|
createdAt: now
|
|
23482
23639
|
}).run();
|
|
23483
23640
|
opts.onBacklinkExtractRequested(runId, project.id, release);
|
|
23484
|
-
const run = app.db.select().from(runs).where(
|
|
23641
|
+
const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
|
|
23485
23642
|
return reply.status(201).send(mapRunRow(run));
|
|
23486
23643
|
});
|
|
23487
23644
|
app.get(
|
|
@@ -23506,15 +23663,15 @@ async function backlinksRoutes(app, opts) {
|
|
|
23506
23663
|
const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
|
|
23507
23664
|
const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
|
|
23508
23665
|
const baseDomainCondition = and18(
|
|
23509
|
-
|
|
23510
|
-
|
|
23666
|
+
eq24(backlinkDomains.projectId, project.id),
|
|
23667
|
+
eq24(backlinkDomains.release, targetRelease)
|
|
23511
23668
|
);
|
|
23512
23669
|
const domainCondition = excludeCrawlers ? and18(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
|
|
23513
23670
|
const totalRow = app.db.select({ count: sql10`count(*)` }).from(backlinkDomains).where(domainCondition).get();
|
|
23514
23671
|
const rows = app.db.select({
|
|
23515
23672
|
linkingDomain: backlinkDomains.linkingDomain,
|
|
23516
23673
|
numHosts: backlinkDomains.numHosts
|
|
23517
|
-
}).from(backlinkDomains).where(domainCondition).orderBy(
|
|
23674
|
+
}).from(backlinkDomains).where(domainCondition).orderBy(desc13(backlinkDomains.numHosts)).limit(limit).offset(offset).all();
|
|
23518
23675
|
let summary = null;
|
|
23519
23676
|
if (summaryRow) {
|
|
23520
23677
|
summary = excludeCrawlers ? computeFilteredSummary(app.db, summaryRow) : mapSummaryRow(summaryRow);
|
|
@@ -23530,7 +23687,7 @@ async function backlinksRoutes(app, opts) {
|
|
|
23530
23687
|
"/projects/:name/backlinks/history",
|
|
23531
23688
|
async (request, reply) => {
|
|
23532
23689
|
const project = resolveProject(app.db, request.params.name);
|
|
23533
|
-
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();
|
|
23534
23691
|
const response = rows.map((r) => ({
|
|
23535
23692
|
release: r.release,
|
|
23536
23693
|
totalLinkingDomains: r.totalLinkingDomains,
|
|
@@ -23544,12 +23701,12 @@ async function backlinksRoutes(app, opts) {
|
|
|
23544
23701
|
}
|
|
23545
23702
|
|
|
23546
23703
|
// ../api-routes/src/traffic.ts
|
|
23547
|
-
import
|
|
23704
|
+
import crypto23 from "crypto";
|
|
23548
23705
|
import { Agent as UndiciAgent } from "undici";
|
|
23549
|
-
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";
|
|
23550
23707
|
|
|
23551
23708
|
// ../integration-cloud-run/src/auth.ts
|
|
23552
|
-
import
|
|
23709
|
+
import crypto22 from "crypto";
|
|
23553
23710
|
var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
|
|
23554
23711
|
var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
|
|
23555
23712
|
var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -23578,7 +23735,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
|
|
|
23578
23735
|
const headerB64 = encode(header);
|
|
23579
23736
|
const payloadB64 = encode(payload);
|
|
23580
23737
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
23581
|
-
const sign =
|
|
23738
|
+
const sign = crypto22.createSign("RSA-SHA256");
|
|
23582
23739
|
sign.update(signingInput);
|
|
23583
23740
|
const signature = sign.sign(privateKey, "base64url");
|
|
23584
23741
|
return `${signingInput}.${signature}`;
|
|
@@ -27339,8 +27496,8 @@ async function runBackfillTask(options) {
|
|
|
27339
27496
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27340
27497
|
try {
|
|
27341
27498
|
app.db.transaction((tx) => {
|
|
27342
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
27343
|
-
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();
|
|
27344
27501
|
});
|
|
27345
27502
|
} catch {
|
|
27346
27503
|
}
|
|
@@ -27355,7 +27512,7 @@ async function runBackfillTask(options) {
|
|
|
27355
27512
|
if (allEvents.length === 0) {
|
|
27356
27513
|
const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
27357
27514
|
try {
|
|
27358
|
-
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();
|
|
27359
27516
|
} catch {
|
|
27360
27517
|
}
|
|
27361
27518
|
return;
|
|
@@ -27378,28 +27535,28 @@ async function runBackfillTask(options) {
|
|
|
27378
27535
|
app.db.transaction((tx) => {
|
|
27379
27536
|
tx.delete(crawlerEventsHourly).where(
|
|
27380
27537
|
and19(
|
|
27381
|
-
|
|
27538
|
+
eq25(crawlerEventsHourly.sourceId, sourceRow.id),
|
|
27382
27539
|
gte3(crawlerEventsHourly.tsHour, windowStartIso),
|
|
27383
27540
|
lte2(crawlerEventsHourly.tsHour, windowEndIso)
|
|
27384
27541
|
)
|
|
27385
27542
|
).run();
|
|
27386
27543
|
tx.delete(aiUserFetchEventsHourly).where(
|
|
27387
27544
|
and19(
|
|
27388
|
-
|
|
27545
|
+
eq25(aiUserFetchEventsHourly.sourceId, sourceRow.id),
|
|
27389
27546
|
gte3(aiUserFetchEventsHourly.tsHour, windowStartIso),
|
|
27390
27547
|
lte2(aiUserFetchEventsHourly.tsHour, windowEndIso)
|
|
27391
27548
|
)
|
|
27392
27549
|
).run();
|
|
27393
27550
|
tx.delete(aiReferralEventsHourly).where(
|
|
27394
27551
|
and19(
|
|
27395
|
-
|
|
27552
|
+
eq25(aiReferralEventsHourly.sourceId, sourceRow.id),
|
|
27396
27553
|
gte3(aiReferralEventsHourly.tsHour, windowStartIso),
|
|
27397
27554
|
lte2(aiReferralEventsHourly.tsHour, windowEndIso)
|
|
27398
27555
|
)
|
|
27399
27556
|
).run();
|
|
27400
27557
|
tx.delete(rawEventSamples).where(
|
|
27401
27558
|
and19(
|
|
27402
|
-
|
|
27559
|
+
eq25(rawEventSamples.sourceId, sourceRow.id),
|
|
27403
27560
|
gte3(rawEventSamples.ts, windowStartIso),
|
|
27404
27561
|
lte2(rawEventSamples.ts, windowEndIso)
|
|
27405
27562
|
)
|
|
@@ -27464,7 +27621,7 @@ async function runBackfillTask(options) {
|
|
|
27464
27621
|
}
|
|
27465
27622
|
})();
|
|
27466
27623
|
tx.insert(rawEventSamples).values({
|
|
27467
|
-
id:
|
|
27624
|
+
id: crypto23.randomUUID(),
|
|
27468
27625
|
projectId: project.id,
|
|
27469
27626
|
sourceId: sourceRow.id,
|
|
27470
27627
|
ts: sample.observedAt,
|
|
@@ -27488,8 +27645,8 @@ async function runBackfillTask(options) {
|
|
|
27488
27645
|
lastError: null,
|
|
27489
27646
|
lastEventIds: newRingBuffer,
|
|
27490
27647
|
updatedAt: finishedAt
|
|
27491
|
-
}).where(
|
|
27492
|
-
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();
|
|
27493
27650
|
});
|
|
27494
27651
|
} catch (e) {
|
|
27495
27652
|
markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -27570,7 +27727,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27570
27727
|
createdAt: existing?.createdAt ?? now,
|
|
27571
27728
|
updatedAt: now
|
|
27572
27729
|
});
|
|
27573
|
-
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);
|
|
27574
27731
|
const config = {
|
|
27575
27732
|
gcpProjectId,
|
|
27576
27733
|
serviceName: serviceName ?? null,
|
|
@@ -27586,10 +27743,10 @@ async function trafficRoutes(app, opts) {
|
|
|
27586
27743
|
lastError: null,
|
|
27587
27744
|
configJson: config,
|
|
27588
27745
|
updatedAt: now
|
|
27589
|
-
}).where(
|
|
27590
|
-
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();
|
|
27591
27748
|
} else {
|
|
27592
|
-
const newId =
|
|
27749
|
+
const newId = crypto23.randomUUID();
|
|
27593
27750
|
app.db.insert(trafficSources).values({
|
|
27594
27751
|
id: newId,
|
|
27595
27752
|
projectId: project.id,
|
|
@@ -27604,7 +27761,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27604
27761
|
createdAt: now,
|
|
27605
27762
|
updatedAt: now
|
|
27606
27763
|
}).run();
|
|
27607
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27764
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
|
|
27608
27765
|
}
|
|
27609
27766
|
writeAuditLog(app.db, {
|
|
27610
27767
|
projectId: project.id,
|
|
@@ -27656,7 +27813,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27656
27813
|
createdAt: existing?.createdAt ?? now,
|
|
27657
27814
|
updatedAt: now
|
|
27658
27815
|
});
|
|
27659
|
-
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);
|
|
27660
27817
|
const config = { baseUrl, username };
|
|
27661
27818
|
const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
|
|
27662
27819
|
let sourceRow;
|
|
@@ -27667,10 +27824,10 @@ async function trafficRoutes(app, opts) {
|
|
|
27667
27824
|
lastError: null,
|
|
27668
27825
|
configJson: config,
|
|
27669
27826
|
updatedAt: now
|
|
27670
|
-
}).where(
|
|
27671
|
-
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();
|
|
27672
27829
|
} else {
|
|
27673
|
-
const newId =
|
|
27830
|
+
const newId = crypto23.randomUUID();
|
|
27674
27831
|
app.db.insert(trafficSources).values({
|
|
27675
27832
|
id: newId,
|
|
27676
27833
|
projectId: project.id,
|
|
@@ -27685,7 +27842,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27685
27842
|
createdAt: now,
|
|
27686
27843
|
updatedAt: now
|
|
27687
27844
|
}).run();
|
|
27688
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27845
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
|
|
27689
27846
|
}
|
|
27690
27847
|
writeAuditLog(app.db, {
|
|
27691
27848
|
projectId: project.id,
|
|
@@ -27739,7 +27896,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27739
27896
|
createdAt: existing?.createdAt ?? now,
|
|
27740
27897
|
updatedAt: now
|
|
27741
27898
|
});
|
|
27742
|
-
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);
|
|
27743
27900
|
const config = { projectId, teamId, environment };
|
|
27744
27901
|
const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
|
|
27745
27902
|
let sourceRow;
|
|
@@ -27750,10 +27907,10 @@ async function trafficRoutes(app, opts) {
|
|
|
27750
27907
|
lastError: null,
|
|
27751
27908
|
configJson: config,
|
|
27752
27909
|
updatedAt: now
|
|
27753
|
-
}).where(
|
|
27754
|
-
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();
|
|
27755
27912
|
} else {
|
|
27756
|
-
const newId =
|
|
27913
|
+
const newId = crypto23.randomUUID();
|
|
27757
27914
|
app.db.insert(trafficSources).values({
|
|
27758
27915
|
id: newId,
|
|
27759
27916
|
projectId: project.id,
|
|
@@ -27776,7 +27933,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27776
27933
|
createdAt: now,
|
|
27777
27934
|
updatedAt: now
|
|
27778
27935
|
}).run();
|
|
27779
|
-
sourceRow = app.db.select().from(trafficSources).where(
|
|
27936
|
+
sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, newId)).get();
|
|
27780
27937
|
}
|
|
27781
27938
|
writeAuditLog(app.db, {
|
|
27782
27939
|
projectId: project.id,
|
|
@@ -27789,7 +27946,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27789
27946
|
});
|
|
27790
27947
|
app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
|
|
27791
27948
|
const project = resolveProject(app.db, request.params.name);
|
|
27792
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
27949
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
|
|
27793
27950
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
27794
27951
|
throw notFound("Traffic source", request.params.id);
|
|
27795
27952
|
}
|
|
@@ -27801,7 +27958,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27801
27958
|
const windowEnd = /* @__PURE__ */ new Date();
|
|
27802
27959
|
const startedAt = windowEnd.toISOString();
|
|
27803
27960
|
const syncStartedAtMs = windowEnd.getTime();
|
|
27804
|
-
const runId =
|
|
27961
|
+
const runId = crypto23.randomUUID();
|
|
27805
27962
|
app.db.insert(runs).values({
|
|
27806
27963
|
id: runId,
|
|
27807
27964
|
projectId: project.id,
|
|
@@ -27815,8 +27972,8 @@ async function trafficRoutes(app, opts) {
|
|
|
27815
27972
|
const markFailed = (msg, errorCode) => {
|
|
27816
27973
|
const failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27817
27974
|
app.db.transaction((tx) => {
|
|
27818
|
-
tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(
|
|
27819
|
-
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();
|
|
27820
27977
|
});
|
|
27821
27978
|
try {
|
|
27822
27979
|
opts.onTrafficSynced?.({
|
|
@@ -27896,7 +28053,7 @@ async function trafficRoutes(app, opts) {
|
|
|
27896
28053
|
}
|
|
27897
28054
|
const credential = credentialStore.getConnection(project.name);
|
|
27898
28055
|
if (!credential) {
|
|
27899
|
-
app.db.delete(runs).where(
|
|
28056
|
+
app.db.delete(runs).where(eq25(runs.id, runId)).run();
|
|
27900
28057
|
throw validationError(
|
|
27901
28058
|
`No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
|
|
27902
28059
|
);
|
|
@@ -27945,12 +28102,12 @@ async function trafficRoutes(app, opts) {
|
|
|
27945
28102
|
auditAction = "traffic.vercel.synced";
|
|
27946
28103
|
const credentialStore = opts.vercelTrafficCredentialStore;
|
|
27947
28104
|
if (!credentialStore) {
|
|
27948
|
-
app.db.delete(runs).where(
|
|
28105
|
+
app.db.delete(runs).where(eq25(runs.id, runId)).run();
|
|
27949
28106
|
throw validationError("Vercel traffic credential storage is not configured for this deployment");
|
|
27950
28107
|
}
|
|
27951
28108
|
const credential = credentialStore.getConnection(project.name);
|
|
27952
28109
|
if (!credential) {
|
|
27953
|
-
app.db.delete(runs).where(
|
|
28110
|
+
app.db.delete(runs).where(eq25(runs.id, runId)).run();
|
|
27954
28111
|
throw validationError(
|
|
27955
28112
|
`No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
|
|
27956
28113
|
);
|
|
@@ -28010,7 +28167,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28010
28167
|
let aiReferralHitsCount = 0;
|
|
28011
28168
|
let unknownHitsCount = 0;
|
|
28012
28169
|
app.db.transaction((tx) => {
|
|
28013
|
-
const latestRow = tx.select().from(trafficSources).where(
|
|
28170
|
+
const latestRow = tx.select().from(trafficSources).where(eq25(trafficSources.id, sourceRow.id)).get();
|
|
28014
28171
|
const previousIds = latestRow.lastEventIds ?? [];
|
|
28015
28172
|
const seenEventIds = new Set(previousIds);
|
|
28016
28173
|
const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
|
|
@@ -28143,7 +28300,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28143
28300
|
}
|
|
28144
28301
|
})();
|
|
28145
28302
|
tx.insert(rawEventSamples).values({
|
|
28146
|
-
id:
|
|
28303
|
+
id: crypto23.randomUUID(),
|
|
28147
28304
|
projectId: project.id,
|
|
28148
28305
|
sourceId: sourceRow.id,
|
|
28149
28306
|
ts: sample.observedAt,
|
|
@@ -28176,8 +28333,8 @@ async function trafficRoutes(app, opts) {
|
|
|
28176
28333
|
if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
|
|
28177
28334
|
sourceUpdate.lastCursor = nextCursor ?? null;
|
|
28178
28335
|
}
|
|
28179
|
-
tx.update(trafficSources).set(sourceUpdate).where(
|
|
28180
|
-
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();
|
|
28181
28338
|
});
|
|
28182
28339
|
writeAuditLog(app.db, {
|
|
28183
28340
|
projectId: project.id,
|
|
@@ -28227,7 +28384,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28227
28384
|
});
|
|
28228
28385
|
app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
|
|
28229
28386
|
const project = resolveProject(app.db, request.params.name);
|
|
28230
|
-
const sourceRow = app.db.select().from(trafficSources).where(
|
|
28387
|
+
const sourceRow = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
|
|
28231
28388
|
if (!sourceRow || sourceRow.projectId !== project.id) {
|
|
28232
28389
|
throw notFound("Traffic source", request.params.id);
|
|
28233
28390
|
}
|
|
@@ -28377,7 +28534,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28377
28534
|
};
|
|
28378
28535
|
}
|
|
28379
28536
|
const startedAt = windowEnd.toISOString();
|
|
28380
|
-
const runId =
|
|
28537
|
+
const runId = crypto23.randomUUID();
|
|
28381
28538
|
app.db.insert(runs).values({
|
|
28382
28539
|
id: runId,
|
|
28383
28540
|
projectId: project.id,
|
|
@@ -28413,35 +28570,35 @@ async function trafficRoutes(app, opts) {
|
|
|
28413
28570
|
function buildSourceDetail(projectId, row, since) {
|
|
28414
28571
|
const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
28415
28572
|
and19(
|
|
28416
|
-
|
|
28573
|
+
eq25(crawlerEventsHourly.sourceId, row.id),
|
|
28417
28574
|
gte3(crawlerEventsHourly.tsHour, since)
|
|
28418
28575
|
)
|
|
28419
28576
|
).get();
|
|
28420
28577
|
const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
|
|
28421
28578
|
and19(
|
|
28422
|
-
|
|
28579
|
+
eq25(aiUserFetchEventsHourly.sourceId, row.id),
|
|
28423
28580
|
gte3(aiUserFetchEventsHourly.tsHour, since)
|
|
28424
28581
|
)
|
|
28425
28582
|
).get();
|
|
28426
28583
|
const aiTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
28427
28584
|
and19(
|
|
28428
|
-
|
|
28585
|
+
eq25(aiReferralEventsHourly.sourceId, row.id),
|
|
28429
28586
|
gte3(aiReferralEventsHourly.tsHour, since)
|
|
28430
28587
|
)
|
|
28431
28588
|
).get();
|
|
28432
28589
|
const sampleTotals = app.db.select({ total: sql11`COUNT(*)` }).from(rawEventSamples).where(
|
|
28433
28590
|
and19(
|
|
28434
|
-
|
|
28591
|
+
eq25(rawEventSamples.sourceId, row.id),
|
|
28435
28592
|
gte3(rawEventSamples.ts, since)
|
|
28436
28593
|
)
|
|
28437
28594
|
).get();
|
|
28438
28595
|
const latestRun = app.db.select().from(runs).where(
|
|
28439
28596
|
and19(
|
|
28440
|
-
|
|
28441
|
-
|
|
28442
|
-
|
|
28597
|
+
eq25(runs.projectId, projectId),
|
|
28598
|
+
eq25(runs.kind, RunKinds["traffic-sync"]),
|
|
28599
|
+
eq25(runs.sourceId, row.id)
|
|
28443
28600
|
)
|
|
28444
|
-
).orderBy(
|
|
28601
|
+
).orderBy(desc14(runs.startedAt)).limit(1).get();
|
|
28445
28602
|
return {
|
|
28446
28603
|
...rowToDto(row),
|
|
28447
28604
|
totals24h: {
|
|
@@ -28467,7 +28624,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28467
28624
|
"`advanceToNow` must be `true`. There is no implicit reset."
|
|
28468
28625
|
);
|
|
28469
28626
|
}
|
|
28470
|
-
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();
|
|
28471
28628
|
if (!sourceRow) {
|
|
28472
28629
|
throw notFound("traffic source", request.params.id);
|
|
28473
28630
|
}
|
|
@@ -28484,7 +28641,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28484
28641
|
status: TrafficSourceStatuses.connected,
|
|
28485
28642
|
lastError: null,
|
|
28486
28643
|
updatedAt: now
|
|
28487
|
-
}).where(
|
|
28644
|
+
}).where(eq25(trafficSources.id, sourceRow.id)).run();
|
|
28488
28645
|
writeAuditLog(tx, auditFromRequest(request, {
|
|
28489
28646
|
projectId: project.id,
|
|
28490
28647
|
actor: "api",
|
|
@@ -28492,20 +28649,20 @@ async function trafficRoutes(app, opts) {
|
|
|
28492
28649
|
entityType: "traffic_source",
|
|
28493
28650
|
entityId: sourceRow.id
|
|
28494
28651
|
}));
|
|
28495
|
-
updatedRow = tx.select().from(trafficSources).where(
|
|
28652
|
+
updatedRow = tx.select().from(trafficSources).where(eq25(trafficSources.id, sourceRow.id)).get();
|
|
28496
28653
|
});
|
|
28497
28654
|
return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
|
|
28498
28655
|
});
|
|
28499
28656
|
app.get("/projects/:name/traffic/sources", async (request) => {
|
|
28500
28657
|
const project = resolveProject(app.db, request.params.name);
|
|
28501
|
-
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();
|
|
28502
28659
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
|
|
28503
28660
|
const response = { sources };
|
|
28504
28661
|
return response;
|
|
28505
28662
|
});
|
|
28506
28663
|
app.get("/projects/:name/traffic/status", async (request) => {
|
|
28507
28664
|
const project = resolveProject(app.db, request.params.name);
|
|
28508
|
-
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();
|
|
28509
28666
|
const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
|
|
28510
28667
|
const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
|
|
28511
28668
|
const response = { sources };
|
|
@@ -28515,7 +28672,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28515
28672
|
"/projects/:name/traffic/sources/:id",
|
|
28516
28673
|
async (request) => {
|
|
28517
28674
|
const project = resolveProject(app.db, request.params.name);
|
|
28518
|
-
const row = app.db.select().from(trafficSources).where(
|
|
28675
|
+
const row = app.db.select().from(trafficSources).where(eq25(trafficSources.id, request.params.id)).get();
|
|
28519
28676
|
if (!row || row.projectId !== project.id) {
|
|
28520
28677
|
throw notFound("Traffic source", request.params.id);
|
|
28521
28678
|
}
|
|
@@ -28566,15 +28723,15 @@ async function trafficRoutes(app, opts) {
|
|
|
28566
28723
|
let aiReferralTotal = 0;
|
|
28567
28724
|
if (kind === "all" || kind === TrafficEventKinds.crawler) {
|
|
28568
28725
|
const crawlerFilters = [
|
|
28569
|
-
|
|
28726
|
+
eq25(crawlerEventsHourly.projectId, project.id),
|
|
28570
28727
|
gte3(crawlerEventsHourly.tsHour, sinceIso),
|
|
28571
28728
|
lte2(crawlerEventsHourly.tsHour, untilIso)
|
|
28572
28729
|
];
|
|
28573
|
-
if (sourceIdParam) crawlerFilters.push(
|
|
28730
|
+
if (sourceIdParam) crawlerFilters.push(eq25(crawlerEventsHourly.sourceId, sourceIdParam));
|
|
28574
28731
|
const crawlerWhere = and19(...crawlerFilters);
|
|
28575
28732
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
|
|
28576
28733
|
crawlerTotal = Number(total?.total ?? 0);
|
|
28577
|
-
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();
|
|
28578
28735
|
for (const r of rows) {
|
|
28579
28736
|
events.push({
|
|
28580
28737
|
kind: TrafficEventKinds.crawler,
|
|
@@ -28591,15 +28748,15 @@ async function trafficRoutes(app, opts) {
|
|
|
28591
28748
|
}
|
|
28592
28749
|
if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
|
|
28593
28750
|
const userFetchFilters = [
|
|
28594
|
-
|
|
28751
|
+
eq25(aiUserFetchEventsHourly.projectId, project.id),
|
|
28595
28752
|
gte3(aiUserFetchEventsHourly.tsHour, sinceIso),
|
|
28596
28753
|
lte2(aiUserFetchEventsHourly.tsHour, untilIso)
|
|
28597
28754
|
];
|
|
28598
|
-
if (sourceIdParam) userFetchFilters.push(
|
|
28755
|
+
if (sourceIdParam) userFetchFilters.push(eq25(aiUserFetchEventsHourly.sourceId, sourceIdParam));
|
|
28599
28756
|
const userFetchWhere = and19(...userFetchFilters);
|
|
28600
28757
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
|
|
28601
28758
|
aiUserFetchTotal = Number(total?.total ?? 0);
|
|
28602
|
-
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();
|
|
28603
28760
|
for (const r of rows) {
|
|
28604
28761
|
events.push({
|
|
28605
28762
|
kind: TrafficEventKinds["ai-user-fetch"],
|
|
@@ -28616,15 +28773,15 @@ async function trafficRoutes(app, opts) {
|
|
|
28616
28773
|
}
|
|
28617
28774
|
if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
|
|
28618
28775
|
const aiFilters = [
|
|
28619
|
-
|
|
28776
|
+
eq25(aiReferralEventsHourly.projectId, project.id),
|
|
28620
28777
|
gte3(aiReferralEventsHourly.tsHour, sinceIso),
|
|
28621
28778
|
lte2(aiReferralEventsHourly.tsHour, untilIso)
|
|
28622
28779
|
];
|
|
28623
|
-
if (sourceIdParam) aiFilters.push(
|
|
28780
|
+
if (sourceIdParam) aiFilters.push(eq25(aiReferralEventsHourly.sourceId, sourceIdParam));
|
|
28624
28781
|
const aiWhere = and19(...aiFilters);
|
|
28625
28782
|
const total = app.db.select({ total: sql11`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(aiWhere).get();
|
|
28626
28783
|
aiReferralTotal = Number(total?.total ?? 0);
|
|
28627
|
-
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();
|
|
28628
28785
|
for (const r of rows) {
|
|
28629
28786
|
events.push({
|
|
28630
28787
|
kind: TrafficEventKinds["ai-referral"],
|
|
@@ -28657,7 +28814,7 @@ async function trafficRoutes(app, opts) {
|
|
|
28657
28814
|
}
|
|
28658
28815
|
|
|
28659
28816
|
// ../api-routes/src/doctor/checks/agent.ts
|
|
28660
|
-
import
|
|
28817
|
+
import crypto24 from "crypto";
|
|
28661
28818
|
import fs6 from "fs";
|
|
28662
28819
|
import path7 from "path";
|
|
28663
28820
|
var REQUIRED_SKILLS = ["canonry", "aero"];
|
|
@@ -28810,7 +28967,7 @@ function isInstalled(dir) {
|
|
|
28810
28967
|
}
|
|
28811
28968
|
function hashInstalledFile(filePath) {
|
|
28812
28969
|
try {
|
|
28813
|
-
return
|
|
28970
|
+
return crypto24.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
|
|
28814
28971
|
} catch {
|
|
28815
28972
|
return void 0;
|
|
28816
28973
|
}
|
|
@@ -29112,7 +29269,7 @@ var ga4ConnectionCheck = {
|
|
|
29112
29269
|
var GA_AUTH_CHECKS = [ga4ConnectionCheck];
|
|
29113
29270
|
|
|
29114
29271
|
// ../api-routes/src/doctor/checks/gbp-auth.ts
|
|
29115
|
-
import { and as and20, eq as
|
|
29272
|
+
import { and as and20, eq as eq26 } from "drizzle-orm";
|
|
29116
29273
|
var RECENT_SYNC_WARN_DAYS = 7;
|
|
29117
29274
|
var RECENT_SYNC_FAIL_DAYS = 30;
|
|
29118
29275
|
function skippedNoProject() {
|
|
@@ -29345,7 +29502,7 @@ var recentSyncCheck = {
|
|
|
29345
29502
|
title: "GBP recent sync",
|
|
29346
29503
|
run: (ctx) => {
|
|
29347
29504
|
if (!ctx.project) return skippedNoProject();
|
|
29348
|
-
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();
|
|
29349
29506
|
if (selected.length === 0) {
|
|
29350
29507
|
return {
|
|
29351
29508
|
status: CheckStatuses.skipped,
|
|
@@ -29405,7 +29562,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
|
|
|
29405
29562
|
);
|
|
29406
29563
|
|
|
29407
29564
|
// ../api-routes/src/doctor/checks/places.ts
|
|
29408
|
-
import { eq as
|
|
29565
|
+
import { eq as eq27 } from "drizzle-orm";
|
|
29409
29566
|
var apiKeyCheck = {
|
|
29410
29567
|
id: "gbp.places.api-key",
|
|
29411
29568
|
category: CheckCategories.auth,
|
|
@@ -29450,7 +29607,7 @@ var apiKeyCheck = {
|
|
|
29450
29607
|
details: { tier: cfg.tier }
|
|
29451
29608
|
};
|
|
29452
29609
|
}
|
|
29453
|
-
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();
|
|
29454
29611
|
const selected = rows.filter((r) => r.selected);
|
|
29455
29612
|
const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
|
|
29456
29613
|
const details = {
|
|
@@ -29889,7 +30046,7 @@ var RUNTIME_STATE_CHECKS = [
|
|
|
29889
30046
|
];
|
|
29890
30047
|
|
|
29891
30048
|
// ../api-routes/src/doctor/checks/traffic-source.ts
|
|
29892
|
-
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";
|
|
29893
30050
|
var RECENT_DATA_WARN_DAYS = 7;
|
|
29894
30051
|
var RECENT_DATA_FAIL_DAYS = 30;
|
|
29895
30052
|
function skippedNoProject3() {
|
|
@@ -29904,7 +30061,7 @@ function loadProbes(ctx) {
|
|
|
29904
30061
|
if (!ctx.project) return [];
|
|
29905
30062
|
const rows = ctx.db.select().from(trafficSources).where(
|
|
29906
30063
|
and21(
|
|
29907
|
-
|
|
30064
|
+
eq28(trafficSources.projectId, ctx.project.id),
|
|
29908
30065
|
ne4(trafficSources.status, TrafficSourceStatuses.archived)
|
|
29909
30066
|
)
|
|
29910
30067
|
).all();
|
|
@@ -29985,7 +30142,7 @@ var recentDataCheck = {
|
|
|
29985
30142
|
const recentCrawlers = Number(
|
|
29986
30143
|
ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
29987
30144
|
and21(
|
|
29988
|
-
|
|
30145
|
+
eq28(crawlerEventsHourly.projectId, ctx.project.id),
|
|
29989
30146
|
gte4(crawlerEventsHourly.tsHour, warnCutoff)
|
|
29990
30147
|
)
|
|
29991
30148
|
).get()?.total ?? 0
|
|
@@ -29993,7 +30150,7 @@ var recentDataCheck = {
|
|
|
29993
30150
|
const recentReferrals = Number(
|
|
29994
30151
|
ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
29995
30152
|
and21(
|
|
29996
|
-
|
|
30153
|
+
eq28(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
29997
30154
|
gte4(aiReferralEventsHourly.tsHour, warnCutoff)
|
|
29998
30155
|
)
|
|
29999
30156
|
).get()?.total ?? 0
|
|
@@ -30009,7 +30166,7 @@ var recentDataCheck = {
|
|
|
30009
30166
|
const olderCrawlers = Number(
|
|
30010
30167
|
ctx.db.select({ total: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
|
|
30011
30168
|
and21(
|
|
30012
|
-
|
|
30169
|
+
eq28(crawlerEventsHourly.projectId, ctx.project.id),
|
|
30013
30170
|
gte4(crawlerEventsHourly.tsHour, failCutoff)
|
|
30014
30171
|
)
|
|
30015
30172
|
).get()?.total ?? 0
|
|
@@ -30017,7 +30174,7 @@ var recentDataCheck = {
|
|
|
30017
30174
|
const olderReferrals = Number(
|
|
30018
30175
|
ctx.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
|
|
30019
30176
|
and21(
|
|
30020
|
-
|
|
30177
|
+
eq28(aiReferralEventsHourly.projectId, ctx.project.id),
|
|
30021
30178
|
gte4(aiReferralEventsHourly.tsHour, failCutoff)
|
|
30022
30179
|
)
|
|
30023
30180
|
).get()?.total ?? 0
|
|
@@ -30395,8 +30552,8 @@ async function doctorRoutes(app, opts) {
|
|
|
30395
30552
|
}
|
|
30396
30553
|
|
|
30397
30554
|
// ../api-routes/src/discovery/routes.ts
|
|
30398
|
-
import
|
|
30399
|
-
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";
|
|
30400
30557
|
var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
30401
30558
|
async function discoveryRoutes(app, opts) {
|
|
30402
30559
|
app.post("/projects/:name/discover/run", async (request, reply) => {
|
|
@@ -30429,20 +30586,20 @@ async function discoveryRoutes(app, opts) {
|
|
|
30429
30586
|
const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
|
|
30430
30587
|
const decision = app.db.transaction((tx) => {
|
|
30431
30588
|
const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and22(
|
|
30432
|
-
|
|
30433
|
-
|
|
30589
|
+
eq29(discoverySessions.projectId, project.id),
|
|
30590
|
+
eq29(discoverySessions.icpDescription, icpDescription),
|
|
30434
30591
|
inArray10(discoverySessions.status, [
|
|
30435
30592
|
DiscoverySessionStatuses.queued,
|
|
30436
30593
|
DiscoverySessionStatuses.seeding,
|
|
30437
30594
|
DiscoverySessionStatuses.probing
|
|
30438
30595
|
]),
|
|
30439
30596
|
gte5(discoverySessions.createdAt, ageFloorIso)
|
|
30440
|
-
)).orderBy(
|
|
30597
|
+
)).orderBy(desc15(discoverySessions.createdAt)).get();
|
|
30441
30598
|
if (existing && existing.runId) {
|
|
30442
30599
|
return { reused: true, sessionId: existing.id, runId: existing.runId };
|
|
30443
30600
|
}
|
|
30444
|
-
const sessionId =
|
|
30445
|
-
const runId =
|
|
30601
|
+
const sessionId = crypto25.randomUUID();
|
|
30602
|
+
const runId = crypto25.randomUUID();
|
|
30446
30603
|
tx.insert(discoverySessions).values({
|
|
30447
30604
|
id: sessionId,
|
|
30448
30605
|
projectId: project.id,
|
|
@@ -30500,7 +30657,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30500
30657
|
const project = resolveProject(app.db, request.params.name);
|
|
30501
30658
|
const parsedLimit = parseInt(request.query.limit ?? "", 10);
|
|
30502
30659
|
const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
|
|
30503
|
-
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();
|
|
30504
30661
|
return reply.send(rows.map(serializeSession));
|
|
30505
30662
|
}
|
|
30506
30663
|
);
|
|
@@ -30508,11 +30665,11 @@ async function discoveryRoutes(app, opts) {
|
|
|
30508
30665
|
"/projects/:name/discover/sessions/:id",
|
|
30509
30666
|
async (request, reply) => {
|
|
30510
30667
|
const project = resolveProject(app.db, request.params.name);
|
|
30511
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
30668
|
+
const session = app.db.select().from(discoverySessions).where(eq29(discoverySessions.id, request.params.id)).get();
|
|
30512
30669
|
if (!session || session.projectId !== project.id) {
|
|
30513
30670
|
throw notFound("Discovery session", request.params.id);
|
|
30514
30671
|
}
|
|
30515
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
30672
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq29(discoveryProbes.sessionId, session.id)).all();
|
|
30516
30673
|
const detail = {
|
|
30517
30674
|
...serializeSession(session),
|
|
30518
30675
|
probes: probeRows.map(serializeProbe)
|
|
@@ -30524,12 +30681,12 @@ async function discoveryRoutes(app, opts) {
|
|
|
30524
30681
|
"/projects/:name/discover/sessions/:id/promote",
|
|
30525
30682
|
async (request, reply) => {
|
|
30526
30683
|
const project = resolveProject(app.db, request.params.name);
|
|
30527
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
30684
|
+
const session = app.db.select().from(discoverySessions).where(eq29(discoverySessions.id, request.params.id)).get();
|
|
30528
30685
|
if (!session || session.projectId !== project.id) {
|
|
30529
30686
|
throw notFound("Discovery session", request.params.id);
|
|
30530
30687
|
}
|
|
30531
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
30532
|
-
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());
|
|
30533
30690
|
const seenCompetitors = new Set(existingCompetitors);
|
|
30534
30691
|
const cited = /* @__PURE__ */ new Set();
|
|
30535
30692
|
const aspirational = /* @__PURE__ */ new Set();
|
|
@@ -30558,7 +30715,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30558
30715
|
);
|
|
30559
30716
|
app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
|
|
30560
30717
|
const project = resolveProject(app.db, request.params.name);
|
|
30561
|
-
const session = app.db.select().from(discoverySessions).where(
|
|
30718
|
+
const session = app.db.select().from(discoverySessions).where(eq29(discoverySessions.id, request.params.id)).get();
|
|
30562
30719
|
if (!session || session.projectId !== project.id) {
|
|
30563
30720
|
throw notFound("Discovery session", request.params.id);
|
|
30564
30721
|
}
|
|
@@ -30581,7 +30738,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30581
30738
|
const bucketSet = new Set(buckets);
|
|
30582
30739
|
const includeCompetitors = parsed.data.includeCompetitors ?? true;
|
|
30583
30740
|
const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
|
|
30584
|
-
const probeRows = app.db.select().from(discoveryProbes).where(
|
|
30741
|
+
const probeRows = app.db.select().from(discoveryProbes).where(eq29(discoveryProbes.sessionId, session.id)).all();
|
|
30585
30742
|
const candidateQueries = /* @__PURE__ */ new Set();
|
|
30586
30743
|
for (const probe of probeRows) {
|
|
30587
30744
|
if (!probe.bucket) continue;
|
|
@@ -30589,7 +30746,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30589
30746
|
if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
|
|
30590
30747
|
}
|
|
30591
30748
|
const existingQueries = new Set(
|
|
30592
|
-
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())
|
|
30593
30750
|
);
|
|
30594
30751
|
const promotedQueries = [];
|
|
30595
30752
|
const skippedQueries = [];
|
|
@@ -30605,7 +30762,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30605
30762
|
const skippedCompetitors = [];
|
|
30606
30763
|
if (includeCompetitors) {
|
|
30607
30764
|
const existingCompetitors = new Set(
|
|
30608
|
-
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())
|
|
30609
30766
|
);
|
|
30610
30767
|
const competitorMap = parseCompetitorMap(session.competitorMap);
|
|
30611
30768
|
for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
|
|
@@ -30624,7 +30781,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30624
30781
|
app.db.transaction((tx) => {
|
|
30625
30782
|
for (const query of promotedQueries) {
|
|
30626
30783
|
tx.insert(queries).values({
|
|
30627
|
-
id:
|
|
30784
|
+
id: crypto25.randomUUID(),
|
|
30628
30785
|
projectId: project.id,
|
|
30629
30786
|
query,
|
|
30630
30787
|
provenance,
|
|
@@ -30633,7 +30790,7 @@ async function discoveryRoutes(app, opts) {
|
|
|
30633
30790
|
}
|
|
30634
30791
|
for (const domain of promotedCompetitors) {
|
|
30635
30792
|
tx.insert(competitors).values({
|
|
30636
|
-
id:
|
|
30793
|
+
id: crypto25.randomUUID(),
|
|
30637
30794
|
projectId: project.id,
|
|
30638
30795
|
domain,
|
|
30639
30796
|
provenance,
|
|
@@ -30707,8 +30864,8 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
|
|
|
30707
30864
|
}
|
|
30708
30865
|
|
|
30709
30866
|
// ../api-routes/src/discovery/orchestrate.ts
|
|
30710
|
-
import
|
|
30711
|
-
import { eq as
|
|
30867
|
+
import crypto26 from "crypto";
|
|
30868
|
+
import { eq as eq30 } from "drizzle-orm";
|
|
30712
30869
|
var DEFAULT_DEDUP_THRESHOLD = 0.85;
|
|
30713
30870
|
var DEFAULT_MAX_PROBES = 100;
|
|
30714
30871
|
var ABSOLUTE_MAX_PROBES = 500;
|
|
@@ -30763,7 +30920,7 @@ async function executeDiscovery(opts) {
|
|
|
30763
30920
|
status: DiscoverySessionStatuses.seeding,
|
|
30764
30921
|
dedupThreshold,
|
|
30765
30922
|
startedAt
|
|
30766
|
-
}).where(
|
|
30923
|
+
}).where(eq30(discoverySessions.id, opts.sessionId)).run();
|
|
30767
30924
|
const seedResult = await opts.deps.seed({
|
|
30768
30925
|
project: opts.project,
|
|
30769
30926
|
icpDescription: opts.icpDescription,
|
|
@@ -30783,7 +30940,7 @@ async function executeDiscovery(opts) {
|
|
|
30783
30940
|
seedProvider: seedResult.provider,
|
|
30784
30941
|
seedCountRaw,
|
|
30785
30942
|
seedCount
|
|
30786
|
-
}).where(
|
|
30943
|
+
}).where(eq30(discoverySessions.id, opts.sessionId)).run();
|
|
30787
30944
|
const probeRows = [];
|
|
30788
30945
|
const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
|
|
30789
30946
|
for (const query of probedCanonicals) {
|
|
@@ -30796,7 +30953,7 @@ async function executeDiscovery(opts) {
|
|
|
30796
30953
|
probeRows.push({ citedDomains: probe.citedDomains, bucket });
|
|
30797
30954
|
buckets[bucket]++;
|
|
30798
30955
|
opts.db.insert(discoveryProbes).values({
|
|
30799
|
-
id:
|
|
30956
|
+
id: crypto26.randomUUID(),
|
|
30800
30957
|
sessionId: opts.sessionId,
|
|
30801
30958
|
projectId: opts.project.id,
|
|
30802
30959
|
query,
|
|
@@ -30823,7 +30980,7 @@ async function executeDiscovery(opts) {
|
|
|
30823
30980
|
wastedCount: buckets["wasted-surface"],
|
|
30824
30981
|
competitorMap,
|
|
30825
30982
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30826
|
-
}).where(
|
|
30983
|
+
}).where(eq30(discoverySessions.id, opts.sessionId)).run();
|
|
30827
30984
|
return {
|
|
30828
30985
|
buckets,
|
|
30829
30986
|
competitorMap,
|
|
@@ -30837,7 +30994,7 @@ function markSessionFailed(db, sessionId, error) {
|
|
|
30837
30994
|
status: DiscoverySessionStatuses.failed,
|
|
30838
30995
|
error,
|
|
30839
30996
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
30840
|
-
}).where(
|
|
30997
|
+
}).where(eq30(discoverySessions.id, sessionId)).run();
|
|
30841
30998
|
}
|
|
30842
30999
|
function dedupeStrings(input) {
|
|
30843
31000
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -30944,6 +31101,7 @@ async function apiRoutes(app, opts) {
|
|
|
30944
31101
|
bing: opts.bingSettingsSummary,
|
|
30945
31102
|
onBingUpdate: opts.onBingSettingsUpdate
|
|
30946
31103
|
});
|
|
31104
|
+
await api.register(keysRoutes);
|
|
30947
31105
|
await api.register(snapshotRoutes, {
|
|
30948
31106
|
onSnapshotRequested: opts.onSnapshotRequested
|
|
30949
31107
|
});
|
|
@@ -31165,7 +31323,7 @@ function buildTrafficSourceValidators(opts) {
|
|
|
31165
31323
|
}
|
|
31166
31324
|
|
|
31167
31325
|
// src/intelligence-service.ts
|
|
31168
|
-
import
|
|
31326
|
+
import crypto27 from "crypto";
|
|
31169
31327
|
|
|
31170
31328
|
// src/logger.ts
|
|
31171
31329
|
var IS_TTY = process.stdout.isTTY === true;
|
|
@@ -31397,15 +31555,15 @@ var IntelligenceService = class {
|
|
|
31397
31555
|
analyzeAndPersist(runId, projectId) {
|
|
31398
31556
|
const recentRuns = this.db.select().from(runs).where(
|
|
31399
31557
|
and23(
|
|
31400
|
-
|
|
31401
|
-
or5(
|
|
31558
|
+
eq31(runs.projectId, projectId),
|
|
31559
|
+
or5(eq31(runs.status, "completed"), eq31(runs.status, "partial")),
|
|
31402
31560
|
// Defensive: RunCoordinator already skips probes before this is
|
|
31403
31561
|
// called, but if a future call site invokes analyzeAndPersist
|
|
31404
31562
|
// directly for a probe, probes still must not pollute the
|
|
31405
31563
|
// intelligence window.
|
|
31406
31564
|
ne5(runs.trigger, RunTriggers.probe)
|
|
31407
31565
|
)
|
|
31408
|
-
).orderBy(
|
|
31566
|
+
).orderBy(desc16(runs.finishedAt), desc16(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
|
|
31409
31567
|
if (recentRuns.length === 0) {
|
|
31410
31568
|
log.info("intelligence.skip", { runId, reason: "no completed runs" });
|
|
31411
31569
|
return null;
|
|
@@ -31480,7 +31638,7 @@ var IntelligenceService = class {
|
|
|
31480
31638
|
* Returns the persisted insights so the coordinator can count critical/high.
|
|
31481
31639
|
*/
|
|
31482
31640
|
analyzeAndPersistGbp(runId, projectId) {
|
|
31483
|
-
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();
|
|
31484
31642
|
if (!runRow) {
|
|
31485
31643
|
log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
|
|
31486
31644
|
this.persistGbpInsights(runId, projectId, [], []);
|
|
@@ -31489,8 +31647,8 @@ var IntelligenceService = class {
|
|
|
31489
31647
|
const windowStart = runRow.startedAt ?? runRow.createdAt;
|
|
31490
31648
|
const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
31491
31649
|
const selected = this.db.select().from(gbpLocations).where(and23(
|
|
31492
|
-
|
|
31493
|
-
|
|
31650
|
+
eq31(gbpLocations.projectId, projectId),
|
|
31651
|
+
eq31(gbpLocations.selected, true),
|
|
31494
31652
|
gte6(gbpLocations.syncedAt, windowStart),
|
|
31495
31653
|
lte3(gbpLocations.syncedAt, windowEnd)
|
|
31496
31654
|
)).all();
|
|
@@ -31525,10 +31683,10 @@ var IntelligenceService = class {
|
|
|
31525
31683
|
}
|
|
31526
31684
|
/** Build the per-location signal bundle the GBP analyzer consumes. */
|
|
31527
31685
|
buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
|
|
31528
|
-
const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and23(
|
|
31529
|
-
const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and23(
|
|
31530
|
-
const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and23(
|
|
31531
|
-
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();
|
|
31532
31690
|
const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
|
|
31533
31691
|
const summary = buildGbpSummary({
|
|
31534
31692
|
locationName,
|
|
@@ -31560,7 +31718,7 @@ var IntelligenceService = class {
|
|
|
31560
31718
|
/** Build the month-over-month keyword series for a location from the
|
|
31561
31719
|
* accumulating gbp_keyword_monthly table (latest complete month vs prior). */
|
|
31562
31720
|
buildGbpKeywordTrend(projectId, locationName) {
|
|
31563
|
-
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();
|
|
31564
31722
|
if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
|
|
31565
31723
|
const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
|
|
31566
31724
|
const recentMonth = months[0] ?? null;
|
|
@@ -31591,7 +31749,7 @@ var IntelligenceService = class {
|
|
|
31591
31749
|
*/
|
|
31592
31750
|
persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
|
|
31593
31751
|
const covered = new Set(coveredLocationNames);
|
|
31594
|
-
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();
|
|
31595
31753
|
const staleIds = [];
|
|
31596
31754
|
const dismissedSlots = /* @__PURE__ */ new Set();
|
|
31597
31755
|
for (const row of existing) {
|
|
@@ -31602,7 +31760,7 @@ var IntelligenceService = class {
|
|
|
31602
31760
|
}
|
|
31603
31761
|
this.db.transaction((tx) => {
|
|
31604
31762
|
for (const id of staleIds) {
|
|
31605
|
-
tx.delete(insights).where(
|
|
31763
|
+
tx.delete(insights).where(eq31(insights.id, id)).run();
|
|
31606
31764
|
}
|
|
31607
31765
|
for (const insight of gbpInsights) {
|
|
31608
31766
|
const parsed = parseGbpInsightId(insight.id);
|
|
@@ -31680,7 +31838,7 @@ var IntelligenceService = class {
|
|
|
31680
31838
|
* create per run + aggregate). DB is left untouched.
|
|
31681
31839
|
*/
|
|
31682
31840
|
backfill(projectName, opts, onProgress) {
|
|
31683
|
-
const project = this.db.select().from(projects).where(
|
|
31841
|
+
const project = this.db.select().from(projects).where(eq31(projects.name, projectName)).get();
|
|
31684
31842
|
if (!project) {
|
|
31685
31843
|
throw new Error(`Project "${projectName}" not found`);
|
|
31686
31844
|
}
|
|
@@ -31694,8 +31852,8 @@ var IntelligenceService = class {
|
|
|
31694
31852
|
}
|
|
31695
31853
|
const allRuns = this.db.select().from(runs).where(
|
|
31696
31854
|
and23(
|
|
31697
|
-
|
|
31698
|
-
or5(
|
|
31855
|
+
eq31(runs.projectId, project.id),
|
|
31856
|
+
or5(eq31(runs.status, "completed"), eq31(runs.status, "partial")),
|
|
31699
31857
|
// Backfill must not replay probe runs as if they were real sweeps.
|
|
31700
31858
|
ne5(runs.trigger, RunTriggers.probe)
|
|
31701
31859
|
)
|
|
@@ -31774,7 +31932,7 @@ var IntelligenceService = class {
|
|
|
31774
31932
|
return { processed, skipped, totalInsights };
|
|
31775
31933
|
}
|
|
31776
31934
|
loadTrackedCompetitors(projectId) {
|
|
31777
|
-
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);
|
|
31778
31936
|
}
|
|
31779
31937
|
/**
|
|
31780
31938
|
* Wipe transition signals from an analysis result while keeping health.
|
|
@@ -31795,15 +31953,15 @@ var IntelligenceService = class {
|
|
|
31795
31953
|
}
|
|
31796
31954
|
persistResult(result, runId, projectId) {
|
|
31797
31955
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
31798
|
-
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();
|
|
31799
31957
|
for (const row of existingInsights) {
|
|
31800
31958
|
if (row.dismissed) {
|
|
31801
31959
|
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
31802
31960
|
}
|
|
31803
31961
|
}
|
|
31804
31962
|
this.db.transaction((tx) => {
|
|
31805
|
-
tx.delete(insights).where(
|
|
31806
|
-
tx.delete(healthSnapshots).where(
|
|
31963
|
+
tx.delete(insights).where(eq31(insights.runId, runId)).run();
|
|
31964
|
+
tx.delete(healthSnapshots).where(eq31(healthSnapshots.runId, runId)).run();
|
|
31807
31965
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
31808
31966
|
for (const insight of result.insights) {
|
|
31809
31967
|
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
@@ -31823,7 +31981,7 @@ var IntelligenceService = class {
|
|
|
31823
31981
|
}).run();
|
|
31824
31982
|
}
|
|
31825
31983
|
tx.insert(healthSnapshots).values({
|
|
31826
|
-
id:
|
|
31984
|
+
id: crypto27.randomUUID(),
|
|
31827
31985
|
projectId,
|
|
31828
31986
|
runId,
|
|
31829
31987
|
overallCitedRate: String(result.health.overallCitedRate),
|
|
@@ -31854,14 +32012,14 @@ var IntelligenceService = class {
|
|
|
31854
32012
|
applySeverityTiering(rawInsights, excludeRunId, projectId) {
|
|
31855
32013
|
const regressions = rawInsights.filter((i) => i.type === "regression");
|
|
31856
32014
|
if (regressions.length === 0) return rawInsights;
|
|
31857
|
-
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();
|
|
31858
32016
|
const gscConnected = gscRows.length > 0;
|
|
31859
32017
|
const gscImpressionsByQuery = /* @__PURE__ */ new Map();
|
|
31860
32018
|
for (const row of gscRows) {
|
|
31861
32019
|
const key = row.query.toLowerCase();
|
|
31862
32020
|
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
31863
32021
|
}
|
|
31864
|
-
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();
|
|
31865
32023
|
const locationCount = Math.max(
|
|
31866
32024
|
1,
|
|
31867
32025
|
(projectRow?.locations ?? []).length
|
|
@@ -31869,13 +32027,13 @@ var IntelligenceService = class {
|
|
|
31869
32027
|
const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
|
|
31870
32028
|
const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
|
|
31871
32029
|
and23(
|
|
31872
|
-
|
|
31873
|
-
|
|
31874
|
-
or5(
|
|
32030
|
+
eq31(runs.projectId, projectId),
|
|
32031
|
+
eq31(runs.kind, RunKinds["answer-visibility"]),
|
|
32032
|
+
or5(eq31(runs.status, "completed"), eq31(runs.status, "partial")),
|
|
31875
32033
|
// Defensive — see top of file.
|
|
31876
32034
|
ne5(runs.trigger, RunTriggers.probe)
|
|
31877
32035
|
)
|
|
31878
|
-
).orderBy(
|
|
32036
|
+
).orderBy(desc16(runs.createdAt), desc16(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
|
|
31879
32037
|
const recentGroups = groupRunsByCreatedAt(recentRunRows);
|
|
31880
32038
|
const recentRunIds = [];
|
|
31881
32039
|
const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
|
|
@@ -31891,7 +32049,7 @@ var IntelligenceService = class {
|
|
|
31891
32049
|
const haveHistory = recentRunIds.length > 0;
|
|
31892
32050
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
31893
32051
|
if (haveHistory) {
|
|
31894
|
-
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();
|
|
31895
32053
|
const regressionGroups = /* @__PURE__ */ new Map();
|
|
31896
32054
|
for (const row of priorRows) {
|
|
31897
32055
|
if (!row.runId) continue;
|
|
@@ -31920,7 +32078,7 @@ var IntelligenceService = class {
|
|
|
31920
32078
|
});
|
|
31921
32079
|
}
|
|
31922
32080
|
buildRunData(runId, projectId, completedAt, location = null) {
|
|
31923
|
-
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();
|
|
31924
32082
|
const projectDomains = projectDomainRow ? effectiveDomains({
|
|
31925
32083
|
canonicalDomain: projectDomainRow.canonicalDomain,
|
|
31926
32084
|
ownedDomains: projectDomainRow.ownedDomains
|
|
@@ -31936,7 +32094,7 @@ var IntelligenceService = class {
|
|
|
31936
32094
|
citedDomains: querySnapshots.citedDomains,
|
|
31937
32095
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
31938
32096
|
snapshotLocation: querySnapshots.location
|
|
31939
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
32097
|
+
}).from(querySnapshots).leftJoin(queries, eq31(querySnapshots.queryId, queries.id)).where(eq31(querySnapshots.runId, runId)).all();
|
|
31940
32098
|
const snapshots = [];
|
|
31941
32099
|
let orphanCount = 0;
|
|
31942
32100
|
for (const r of rows) {
|